Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add donation voting merkle distribution strategy #642

Merged
merged 42 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
f074bf0
feat: add donation voting strategy
ilpepepig Aug 1, 2024
a6c4e20
feat: add donation voting with on chain QF
ilpepepig Aug 1, 2024
cbeffe3
refactor: block funding after allocation ended
ilpepepig Aug 1, 2024
62cd5c7
refactor: order of functions
ilpepepig Aug 1, 2024
f2c1039
fix: overflow due to invalid revert
ilpepepig Aug 3, 2024
624ccc5
refactor: optimize
ilpepepig Aug 3, 2024
608febe
refactor: fix timestamp handling
ilpepepig Aug 4, 2024
631eafa
fix: update timestamps
ilpepepig Aug 5, 2024
a937f16
refactor: change visibility of _isPoolTimestampValid to view
ilpepepig Aug 5, 2024
cca0b93
test: add DonationVotingOnchain integration tests
ilpepepig Aug 5, 2024
5055f6b
docs: fix natspec
ilpepepig Aug 5, 2024
09ec853
test: add integration tests for the offchain donation voting strategy
ilpepepig Aug 5, 2024
d8db12f
chore: fix format
ilpepepig Aug 5, 2024
6aa3261
chore: fix format
ilpepepig Aug 6, 2024
367789a
docs: add and fix natspec
ilpepepig Aug 6, 2024
28cbafb
refactor: improve events
ilpepepig Aug 6, 2024
ea277e9
refactor: improve errors
ilpepepig Aug 6, 2024
02a5289
chore: ignore format check for DirectGrantsSimpleStrategy
ilpepepig Aug 7, 2024
0b9fa33
refactor: make claimAllocation and setPayout more generic
ilpepepig Aug 8, 2024
66b38c2
feat: add donation voting strategy with merkle distribution
ilpepepig Aug 8, 2024
76de9b1
refactor: add constructor and fix format
ilpepepig Aug 8, 2024
f638c87
Merge branch 'v2.1' of github.com:allo-protocol/allo-v2 into feat/add…
ilpepepig Aug 14, 2024
0331997
refactor: make claimAllocation and setPayout more generic
ilpepepig Aug 14, 2024
1bda2ca
docs: update natspec
ilpepepig Aug 14, 2024
9f36605
feat: make direct and vault donations a parameter of the same strategy
ilpepepig Aug 14, 2024
8209130
refactor: make directTransfer immutable
ilpepepig Aug 17, 2024
cd91f4f
feat: add permit util function to Transfer.sol and use it in donation…
ilpepepig Aug 18, 2024
b2c4f66
docs: fix natspec
ilpepepig Aug 19, 2024
87a566c
fix: param usage
ilpepepig Aug 19, 2024
d0c1ee3
refactor: reorganize code
ilpepepig Aug 19, 2024
2298612
test: update donation voting offchain strategy integration tests
ilpepepig Aug 19, 2024
d8fbd05
test: add donation voting merkle distribution integration tests
ilpepepig Aug 20, 2024
0d2a119
Merge branch 'v2.1' of github.com:allo-protocol/allo-v2 into feat/add…
ilpepepig Aug 20, 2024
fde1f37
test: fix DonationVotingOffchain full flow test
ilpepepig Aug 20, 2024
c3e2b37
test: inherit from IntegrationBase
ilpepepig Aug 20, 2024
bdc8603
refactor: use remappings instead of relative import paths
ilpepepig Aug 20, 2024
c07a69d
test: move NATIVE declaration to IntegrationBase
ilpepepig Aug 20, 2024
040cee1
refactor: use internal vars and functions instead of private
ilpepepig Aug 20, 2024
c747a59
Merge branch 'v2.1' of github.com:allo-protocol/allo-v2 into feat/add…
ilpepepig Aug 27, 2024
80cc96a
refactor: use Transfer library
ilpepepig Aug 27, 2024
e49ceb0
chore: fix format
ilpepepig Aug 27, 2024
1636073
chore: fix format and natspec
ilpepepig Aug 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions contracts/core/interfaces/IDAI.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;

interface IDAI {
function permit(
address holder,
address spender,
uint256 nonce,
uint256 expiry,
bool allowed,
uint8 v,
bytes32 r,
bytes32 s
) external;
}
82 changes: 82 additions & 0 deletions contracts/core/libraries/Transfer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ pragma solidity 0.8.19;
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
// Internal Libraries
import "./Native.sol";
// Interfaces
import {ISignatureTransfer} from "permit2/ISignatureTransfer.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IDAI} from "contracts/core/interfaces/IDAI.sol";

// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
Expand All @@ -29,13 +34,31 @@ contract Transfer is Native {
/// @notice Thrown when the amount of tokens sent does not match the amount of tokens expected
error AMOUNT_MISMATCH();

/// @notice Thrown if a signature of a length different than 64 or 65 bytes is passed
error INVALID_SIGNATURE_LENGTH();

/// @notice This holds the details for a transfer
struct TransferData {
address from;
address to;
uint256 amount;
}

enum PermitFormat {
None,
Permit,
PermitDAI,
Permit2
}

/// @notice Stores the permit2 data for the allocation
struct PermitData {
ISignatureTransfer permit2;
uint256 nonce;
uint256 deadline;
bytes signature;
}

/// @notice Transfer an amount of a token to an array of addresses
/// @param _token The address of the token
/// @param _transferData TransferData[]
Expand Down Expand Up @@ -107,4 +130,63 @@ contract Transfer is Native {
return SafeTransferLib.balanceOf(_token, _account);
}
}

/// @notice Uses a permit if given. Three permit formats are accepted: permit2, ERC20Permit and DAI-like permits
/// @dev permit data is ignored if empty
/// @param _token The token address
/// @param _from The address signing the permit
/// @param _to The address to give allowance to
/// @param _amount The amount to allow
/// @param _permitData The PermitData containing the signature and relevant permit data
function _usePermit(address _token, address _from, address _to, uint256 _amount, bytes memory _permitData)
internal
{
// Only try to use permit if needed
if (_permitData.length == 0) return;
if (IERC20(_token).allowance(_from, _to) >= _amount) return;

(PermitFormat permitFormat, PermitData memory permit) = abi.decode(_permitData, (PermitFormat, PermitData));
if (permitFormat == PermitFormat.Permit2) {
permit.permit2.permitTransferFrom(
ISignatureTransfer.PermitTransferFrom({
permitted: ISignatureTransfer.TokenPermissions({token: _token, amount: _amount}),
nonce: permit.nonce,
deadline: permit.deadline
}),
ISignatureTransfer.SignatureTransferDetails({to: _to, requestedAmount: _amount}),
_from,
permit.signature
);
} else if (permitFormat == PermitFormat.Permit) {
(bytes32 r, bytes32 s, uint8 v) = _splitSignature(permit.signature);
IERC20Permit(_token).permit(_from, _to, _amount, permit.deadline, v, r, s);
} else if (permitFormat == PermitFormat.PermitDAI) {
(bytes32 r, bytes32 s, uint8 v) = _splitSignature(permit.signature);
IDAI(_token).permit(_from, _to, permit.nonce, permit.deadline, true, v, r, s);
}
}

/// @notice Splits a signature into its r, s, v components
/// @dev compact EIP-2098 signatures are accepted as well
/// @param _signature The signature
function _splitSignature(bytes memory _signature) internal pure returns (bytes32 r, bytes32 s, uint8 v) {
if (_signature.length == 65) {
assembly {
r := mload(add(_signature, 0x20))
s := mload(add(_signature, 0x40))
v := byte(0, mload(add(_signature, 0x60)))
}
} else if (_signature.length == 64) {
// EIP-2098
bytes32 vs;
assembly {
r := mload(add(_signature, 0x20))
vs := mload(add(_signature, 0x40))
}
s = vs & (0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
v = uint8(uint256(vs >> 255)) + 27;
} else {
revert INVALID_SIGNATURE_LENGTH();
}
}
}
191 changes: 191 additions & 0 deletions contracts/strategies/DonationVotingMerkleDistribution.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.19;

// External Libraries
import {MerkleProof} from "openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
// Interfaces
import {IAllo} from "contracts/core/interfaces/IAllo.sol";
// Core Contracts
import {DonationVotingOffchain} from "contracts/strategies/DonationVotingOffchain.sol";
// Internal Libraries
import {Metadata} from "contracts/core/libraries/Metadata.sol";

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

/// @title Donation Voting Strategy with off-chain setup
/// @notice Strategy that allows allocations in multiple tokens to accepted recipient. The actual payouts are set
/// by the pool manager.
contract DonationVotingMerkleDistribution is DonationVotingOffchain {
/// ===============================
/// ========== Events =============
/// ===============================

/// @notice Emitted when the distribution has been updated with a new merkle root or metadata
/// @param merkleRoot The merkle root of the distribution
/// @param metadata The metadata of the distribution
event DistributionUpdated(bytes32 merkleRoot, Metadata metadata);

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

/// @notice Thrown when the merkle root is attempted to be updated but the distribution is ongoing
error DISTRIBUTION_ALREADY_STARTED();

/// @notice Thrown when distribution is invoked but the merkle root has not been set yet
error MERKLE_ROOT_NOT_SET();

/// @notice Thrown when distribution is attempted twice for the same 'index'
error ALREADY_DISTRIBUTED(uint256 _index);

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

/// @notice Stores the details of the distribution.
struct Distribution {
uint256 index;
address recipientId;
uint256 amount;
bytes32[] merkleProof;
}

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

/// @notice Metadata containing the distribution data.
Metadata public distributionMetadata;

/// @notice Flag to indicate whether the distribution has started or not.
bool public distributionStarted;

/// @notice The merkle root of the distribution will be set by the pool manager.
bytes32 public merkleRoot;

/// @notice This is a packed array of booleans to keep track of claims distributed.
/// @dev _distributedBitMap[0] is the first row of the bitmap and allows to store 256 bits to describe
/// the status of 256 claims
mapping(uint256 => uint256) internal _distributedBitMap;

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

/// @notice Constructor for the Donation Voting Offchain strategy
/// @param _allo The 'Allo' contract
/// @param _directTransfer false if allocations must be manually claimed, true if they are sent during allocation.
constructor(address _allo, bool _directTransfer) DonationVotingOffchain(_allo, _directTransfer) {}

/// ===============================
/// ======= External/Custom =======
/// ===============================

/// @notice Invoked by round operator to update the merkle root and distribution Metadata.
/// @param _data The data to be decoded
/// @custom:data (bytes32 _merkleRoot, Metadata _distributionMetadata)
function setPayout(bytes memory _data) external virtual override onlyPoolManager(msg.sender) onlyAfterAllocation {
// The merkleRoot can only be updated before the distribution has started
if (distributionStarted) revert DISTRIBUTION_ALREADY_STARTED();

(bytes32 _merkleRoot, Metadata memory _distributionMetadata) = abi.decode(_data, (bytes32, Metadata));

merkleRoot = _merkleRoot;
distributionMetadata = _distributionMetadata;

emit DistributionUpdated(_merkleRoot, _distributionMetadata);
}

/// @notice Utility function to check if distribution is done.
/// @dev This function doesn't change the state even if it is not marked as 'view'
/// @param _index index of the distribution
/// @return 'true' if distribution is completed, otherwise 'false'
function hasBeenDistributed(uint256 _index) external returns (bool) {
return _distributed(_index, false);
}

/// ====================================
/// ============ Internal ==============
/// ====================================

/// @notice Distributes funds (tokens) to recipients.
/// @param _data Data to be decoded
/// @custom:data (Distribution[] _distributions)
/// @param _sender The address of the sender
function _distribute(address[] memory, bytes memory _data, address _sender)
internal
virtual
override
onlyAfterAllocation
{
if (merkleRoot == bytes32(0)) revert MERKLE_ROOT_NOT_SET();

if (!distributionStarted) distributionStarted = true;

// Loop through the distributions and distribute the funds
Distribution[] memory distributions = abi.decode(_data, (Distribution[]));
IAllo.Pool memory pool = allo.getPool(poolId);
for (uint256 i; i < distributions.length;) {
_distributeSingle(distributions[i], pool.token, _sender);
unchecked {
i++;
}
}
}

/// @notice Check if the distribution has been distributed.
/// @param _index index of the distribution
/// @param _set if 'true' sets the '_distributedBitMap' index to 'true', otherwise it is left unmodified
/// @return 'true' if the distribution has been distributed, otherwise 'false'
function _distributed(uint256 _index, bool _set) internal returns (bool) {
uint256 wordIndex = _index / 256;
uint256 distributedWord = _distributedBitMap[wordIndex];

uint256 bitIndex = _index % 256;
// Get the mask by shifting 1 to the left of the 'bitIndex'
uint256 mask = (1 << bitIndex);

// Set the 'bitIndex' of 'distributedWord' to 1
if (_set) _distributedBitMap[wordIndex] = distributedWord | (1 << bitIndex);

// Return 'true' if the 'distributedWord' was 1 at 'bitIndex'
return distributedWord & mask == mask;
}

/// @notice Distribute funds to recipient.
/// @dev Emits a 'Distributed()' event per distributed recipient
/// @param _distribution Distribution to be distributed
/// @param _poolToken Token address of the strategy
/// @param _sender The address of the sender
function _distributeSingle(Distribution memory _distribution, address _poolToken, address _sender) internal {
if (!_isAcceptedRecipient(_distribution.recipientId)) revert RECIPIENT_NOT_ACCEPTED();

// Generate the node that will be verified in the 'merkleRoot'
bytes32 node = keccak256(abi.encode(_distribution.index, _distribution.recipientId, _distribution.amount));

// Validate the distribution and transfer the funds to the recipient, otherwise skip
if (MerkleProof.verify(_distribution.merkleProof, merkleRoot, node)) {
if (_distributed(_distribution.index, true)) revert ALREADY_DISTRIBUTED(_distribution.index);
poolAmount -= _distribution.amount;

address recipientAddress = _recipients[_distribution.recipientId].recipientAddress;
_transferAmount(_poolToken, recipientAddress, _distribution.amount);

emit Distributed(_distribution.recipientId, abi.encode(recipientAddress, _distribution.amount, _sender));
}
}
}
Loading
Loading