Skip to content

Commit

Permalink
Merge branch 'v2.1' of github.com:allo-protocol/allo-v2 into feat/imp…
Browse files Browse the repository at this point in the history
…rove-transfer-library
  • Loading branch information
ilpepepig committed Aug 23, 2024
2 parents 606ea5c + 5bae660 commit 171dab9
Show file tree
Hide file tree
Showing 8 changed files with 696 additions and 7 deletions.
3 changes: 3 additions & 0 deletions contracts/core/interfaces/IBaseStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ interface IBaseStrategy {
/// @notice Error when the pool ID is invalid
error BaseStrategy_INVALID_POOL_ID();

/// @notice Error when the withdraw amount leaves the pool with insufficient funds
error BaseStrategy_WITHDRAW_MORE_THAN_POOL_AMOUNT();

/// ======================
/// ======= Events =======
/// ======================
Expand Down
4 changes: 4 additions & 0 deletions contracts/strategies/CoreBaseStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ abstract contract CoreBaseStrategy is IBaseStrategy {
onlyPoolManager(msg.sender)
{
_beforeWithdraw(_token, _amount, _recipient);
// If the token is the pool token, revert if the amount is greater than the pool amount
if (_token.getBalance(address(this)) - _amount < poolAmount) {
revert BaseStrategy_WITHDRAW_MORE_THAN_POOL_AMOUNT();
}
_token.transferAmount(_recipient, _amount);
_afterWithdraw(_token, _amount, _recipient);

Expand Down
193 changes: 193 additions & 0 deletions contracts/strategies/QVImpactStream.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.19;

// External Libraries
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

// Interfaces
import {IAllo} from "../core/interfaces/IAllo.sol";
// Core Contracts
import {QVSimple} from "./QVSimple.sol";

// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⣿⢿⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⣿⣿⣿⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⡟⠘⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
// ⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣾⣿⣿⣿⣿⣾⠻⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⡿⠀⠀⠸⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⢀⣠⣴⣴⣶⣶⣶⣦⣦⣀⡀⠀⠀⠀⠀⠀⠀
// ⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⣿⣿⡿⠃⠀⠙⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⠁⠀⠀⠀⢻⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⡀⠀⠀⠀⠀
// ⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⣿⡿⠁⠀⠀⠀⠘⣿⣿⣿⣿⣿⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⠃⠀⠀⠀⠀⠈⢿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⣰⣿⣿⣿⡿⠋⠁⠀⠀⠈⠘⠹⣿⣿⣿⣿⣆⠀⠀⠀
// ⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⣿⡿⠀⠀⠀⠀⠀⠀⠈⢿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⢰⣿⣿⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⡀⠀⠀
// ⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣟⠀⡀⢀⠀⡀⢀⠀⡀⢈⢿⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⡇⠀⠀
// ⠀⠀⣠⣿⣿⣿⣿⣿⣿⡿⠋⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⣿⡿⢿⠿⠿⠿⠿⠿⠿⠿⠿⠿⢿⣿⣿⣿⣷⡀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠸⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⠂⠀⠀
// ⠀⠀⠙⠛⠿⠻⠻⠛⠉⠀⠀⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⣿⣿⣿⣧⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⢻⣿⣿⣿⣷⣀⢀⠀⠀⠀⡀⣰⣾⣿⣿⣿⠏⠀⠀⠀
// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⣧⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠹⢿⣿⣿⣿⣿⣾⣾⣷⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀
// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠙⠋⠛⠙⠋⠛⠙⠋⠛⠙⠋⠃⠀⠀⠀⠀⠀⠀⠀⠀⠠⠿⠻⠟⠿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠟⠿⠟⠿⠆⠀⠸⠿⠿⠟⠯⠀⠀⠀⠸⠿⠿⠿⠏⠀⠀⠀⠀⠀⠈⠉⠻⠻⡿⣿⢿⡿⡿⠿⠛⠁⠀⠀⠀⠀⠀⠀
// allo.gitcoin.co
contract QVImpactStream is QVSimple {
/// ======================
/// ======= Events =======
/// ======================

/// @notice Emitted when the payouts are set
/// @param payouts The payouts to distribute
/// @param sender The sender of the transaction
event PayoutSet(Payout[] payouts, address sender);

/// ======================
/// ======= Errors =======
/// ======================

/// @notice Thrown when payout is already set
error PAYOUT_ALREADY_SET();

/// @notice Thrown when the total set payout is more than the pool balance
error PAYOUT_MORE_THAN_POOL_BALANCE();

/// ======================
/// ======= Storage ======
/// ======================

/// @notice Returns the amount to pay to the recipient
/// @dev recipientId => payouts
mapping(address => uint256) public payouts;

/// @notice Returns true if the payout is set
bool public payoutSet;

/// ======================
/// ======= Struct =======
/// ======================

/// @notice The details of the payout set by the pool managers
struct Payout {
address recipientId;
uint256 amount;
}

/// ====================================
/// ========== Constructor =============
/// ====================================

/// @notice Constructor for the QV Impact Stream strategy
/// @param _allo The 'Allo' contract
constructor(address _allo) QVSimple(_allo) {}

/// ====================================
/// ==== External/Public Functions =====
/// ====================================

/// @notice Add allocator array
/// @dev Only the pool manager(s) can call this function and emits an `AllocatorAdded` event
/// @param _allocators The allocator address array
function batchAddAllocator(address[] memory _allocators) external onlyPoolManager(msg.sender) {
uint256 length = _allocators.length;
for (uint256 i = 0; i < length;) {
_addAllocator(_allocators[i]);

unchecked {
++i;
}
}
}

/// @notice Remove allocator array
/// @dev Only the pool manager(s) can call this function and emits an `AllocatorRemoved` event
/// @param _allocators The allocators address array
function batchRemoveAllocator(address[] memory _allocators) external onlyPoolManager(msg.sender) {
uint256 length = _allocators.length;
for (uint256 i = 0; i < length;) {
_removeAllocator(_allocators[i]);

unchecked {
++i;
}
}
}

/// @notice Set the payouts to distribute
/// @dev Only the pool manager(s) can call this function
/// @param _payouts The payouts to distribute
function setPayouts(Payout[] memory _payouts) external onlyPoolManager(msg.sender) onlyAfterAllocation {
if (payoutSet) revert PAYOUT_ALREADY_SET();
payoutSet = true;

uint256 totalAmount;

uint256 length = _payouts.length;
for (uint256 i = 0; i < length;) {
Payout memory payout = _payouts[i];
uint256 amount = payout.amount;
address recipientId = payout.recipientId;

if (amount == 0 || _getRecipientStatus(recipientId) != Status.Accepted) {
revert RECIPIENT_ERROR(recipientId);
}

payouts[recipientId] = amount;
totalAmount += amount;
unchecked {
++i;
}
}

if (totalAmount > poolAmount) revert PAYOUT_MORE_THAN_POOL_BALANCE();

emit PayoutSet(_payouts, msg.sender);
}

/// =============================
/// ==== Internal Functions =====
/// =============================

function _distribute(address[] memory _recipientIds, bytes memory, address _sender)
internal
virtual
override
onlyAfterAllocation
{
IAllo.Pool memory pool = allo.getPool(poolId);
address poolToken = pool.token;

uint256 length = _recipientIds.length;
for (uint256 i = 0; i < length;) {
address recipientId = _recipientIds[i];
Recipient storage recipient = _recipients[recipientId];

address recipientAddress = recipient.recipientAddress;
uint256 amount = payouts[recipientId];

if (amount == 0) revert RECIPIENT_ERROR(recipientId);

delete payouts[recipientId];

_transferAmount(poolToken, recipientAddress, amount);

bytes memory data = abi.encode(recipientAddress, amount, _sender);

emit Distributed(recipientId, data);

unchecked {
++i;
}
}
}

/// =========================
/// ==== View Functions =====
/// =========================

/// @notice Get the total votes received for a recipient
/// @param _recipient The address of the recipient
/// @return The total votes received by the recipient
function getTotalVotesForRecipient(address _recipient) external view returns (uint256) {
return _votingState.recipientVotes[_recipient];
}

/// @notice Get the payout for a single recipient
/// @param _recipientId The ID of the recipient
/// @return The payout as a 'Payout' struct
function getPayout(address _recipientId) external view returns (Payout memory) {
uint256 amount = payouts[_recipientId];
return Payout(_recipientId, amount);
}
}
28 changes: 21 additions & 7 deletions contracts/strategies/QVSimple.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ contract QVSimple is CoreBaseStrategy, RecipientsExtension {
/// @notice Initialize the strategy
/// @param _poolId The pool id
/// @param _data The data to initialize the strategy (Must include RecipientInitializeData and QVSimpleInitializeData)
function initialize(uint256 _poolId, bytes memory _data) external override {
function initialize(uint256 _poolId, bytes memory _data) external virtual override {
__BaseStrategy_init(_poolId);

(
Expand Down Expand Up @@ -132,18 +132,14 @@ contract QVSimple is CoreBaseStrategy, RecipientsExtension {
/// @dev Only the pool manager(s) can call this function and emits an `AllocatorAdded` event
/// @param _allocator The allocator address
function addAllocator(address _allocator) external onlyPoolManager(msg.sender) {
allowedAllocators[_allocator] = true;

emit AllocatorAdded(_allocator, msg.sender);
_addAllocator(_allocator);
}

/// @notice Remove allocator
/// @dev Only the pool manager(s) can call this function and emits an `AllocatorRemoved` event
/// @param _allocator The allocator address
function removeAllocator(address _allocator) external onlyPoolManager(msg.sender) {
allowedAllocators[_allocator] = false;

emit AllocatorRemoved(_allocator, msg.sender);
_removeAllocator(_allocator);
}

/// ====================================
Expand Down Expand Up @@ -231,6 +227,24 @@ contract QVSimple is CoreBaseStrategy, RecipientsExtension {
voiceCreditsAllocated[_sender] += voiceCreditsToAllocate;
}

/// @notice Add allocator
/// @dev Only the pool manager(s) can call this function and emits an `AllocatorAdded` event
/// @param _allocator The allocator address
function _addAllocator(address _allocator) internal virtual {
allowedAllocators[_allocator] = true;

emit AllocatorAdded(_allocator, msg.sender);
}

/// @notice Remove allocator
/// @dev Only the pool manager(s) can call this function and emits an `AllocatorRemoved` event
/// @param _allocator The allocator address
function _removeAllocator(address _allocator) internal virtual {
allowedAllocators[_allocator] = false;

emit AllocatorRemoved(_allocator, msg.sender);
}

/// @notice Returns if the recipient is accepted
/// @param _recipientId The recipient id
/// @return true if the recipient is accepted
Expand Down
Loading

0 comments on commit 171dab9

Please sign in to comment.