# TCR Comparisons > [TOC] ## Thrive Coin Does not appear to be suitable, it is not a voting platform at all. It is a rewards platform, for rewarding contributions. It appears to me that Plurality used Thank Arb to reward contributors. During gov month, one of the ways they rewarded people was by paying them to vote? It looks to me like they used Jokerace for voting and even snapshot once, eg here is a screenshot of the top contributor for gov month: ![image](https://hackmd.io/_uploads/Sk-G0KFNp.png) **Doesn't appear to be open source and not well documented ## Jokerace [jokerace Github](https://github.com/jk-labs-inc/jokerace) Poorly documented. jokerace borders on amateur other than that, it's pretty swell. ![image](https://hackmd.io/_uploads/H1gnDZ9Ep.png) ![image](https://hackmd.io/_uploads/SJjP_b5NT.png) ![image](https://hackmd.io/_uploads/ByKxYb5Ep.png) ![image](https://hackmd.io/_uploads/HyabKZqEa.png) ![image](https://hackmd.io/_uploads/SyLLFW9V6.png) ![image](https://hackmd.io/_uploads/ryu0KW9Vp.png) **Here's the kicker!!** ![image](https://hackmd.io/_uploads/B1Q-cW946.png) This feature appears to be new: ![image](https://hackmd.io/_uploads/HyJD9W9NT.png) ![image](https://hackmd.io/_uploads/Syqdi-9Np.png) ![image](https://hackmd.io/_uploads/ByjjiW9Ea.png) Deploying Contests won't be cheap ![image](https://hackmd.io/_uploads/rkoM3bcNa.png) ooops, I made a mistake and forgot to make a selection in the "Who Can Submit Field", now it appears this contest is a dud. It let me proceed w/o a selection which is actually worthy of an Issue creation in their github. ![image](https://hackmd.io/_uploads/H1tgp-c4p.png) Overall, I think jokerace fits our needs fairly well. It would be nice if they had an sdk to integrate into custom dapps. ## Snapshot --- ## Allo Merkle donation base strategy Detailed contract breakdown: ### Structs ``` @notice Struct to hold details of the application status @dev Application status is stored in a bitmap. Each 4 bits represents the status of a recipient, defined as 'index' here. The first 4 bits of the 256 bits represent the status of the first recipient, the second 4 bits represent the status of the second recipient, and so on. The 'rowIndex' is the index of the row in the bitmap, and the 'statusRow' is the value of the row. The 'statusRow' is updated when the status of a recipient changes. Note: Since we need 4 bits to store a status, one row of the bitmap can hold the status information of 256/4 recipients. For example, if we have 5 recipients, the bitmap will look like this: | recipient1 | recipient2 | recipient3 | recipient4 | recipient5 | 'rowIndex' | 0000 | 0001 | 0010 | 0011 | 0100 | 'statusRow' | none | pending | accepted | rejected | appealed | converted status (0, 1, 2, 3, 4) ``` 1. **ApplicationStatus**: This struct is used to track the status of applications in a bitmap format. Each application status is represented by 4 bits, allowing multiple statuses to be stored compactly in a single 256-bit integer. - `index`: This represents the index of the recipient in the bitmap. - `statusRow`: The value of the row in the bitmap that represents the status of a group of recipients. Since each status is 4 bits, a single 256-bit row can represent the statuses of up to 64 recipients. The struct allows for efficient storage and retrieval of application statuses, supporting operations like updating statuses or querying the current status of an application. ```solidity struct ApplicationStatus { uint256 index; uint256 statusRow; } ``` 2. **Recipient**: This struct holds details about the recipients of funds. - `useRegistryAnchor`: A boolean indicating whether the recipient's address is the anchor of the profile in the registry. - `recipientAddress`: The Ethereum address of the recipient. - `metadata`: An instance of a `Metadata` struct (not detailed here), which likely contains additional information about the recipient. This struct is for managing recipient details and ensuring that funds are distributed to the correct parties. ```solidity /// @notice Stores the details of the recipients. struct Recipient { // If false, the recipientAddress is the anchor of the profile bool useRegistryAnchor; address recipientAddress; Metadata metadata; ``` 3. **Distribution**: This struct is used for managing the details of fund distribution to recipients. - `index`: An identifier for the distribution. - `recipientId`: The address of the recipient. - `amount`: The amount of funds to be distributed. - `merkleProof`: An array of bytes32 values that make up the Merkle proof, which is necessary for verifying the recipient's entitlement to the funds in a Merkle Tree structure. This struct plays a critical role in ensuring the integrity and verifiability of fund distributions. ```solidity /// @notice Stores the details of the distribution. struct Distribution { uint256 index; address recipientId; uint256 amount; bytes32[] merkleProof; } ``` 4. **InitializeData**: This struct is used to store initial configuration data for the strategy. - `useRegistryAnchor`: Indicates whether to use the registry anchor. - `metadataRequired`: A boolean to indicate if metadata is required. - `registrationStartTime` and `registrationEndTime`: Timestamps defining the start and end of the registration period. - `allocationStartTime` and `allocationEndTime`: Timestamps defining the start and end of the allocation period. - `allowedTokens`: An array of addresses representing the tokens that are allowed to be used. This struct is essential for setting up the initial parameters and rules for the strategy's operation. ```solidity /// @notice Stores the initialize data for the strategy struct InitializeData { bool useRegistryAnchor; bool metadataRequired; uint64 registrationStartTime; uint64 registrationEndTime; uint64 allocationStartTime; uint64 allocationEndTime; address[] allowedTokens; } ``` > [name=boilerrat] Thinking that in a custom contract, the addition of HATS might come in here manage permissions later, eg modifiers may look for a hat in order to call functions. 5. **Permit2Data**: This struct is used for managing permissions for allocations. - `permit`: An instance of a `PermitTransferFrom` struct (likely from the `ISignatureTransfer` interface), which includes details necessary for a permitted transfer. - `signature`: A byte array representing the signature that accompanies the permit. This struct is crucial for handling permissions and authorizations in the context of token transfers, ensuring secure and authorized operations. ```solidity /// @notice Stores the permit2 data for the allocation struct Permit2Data { ISignatureTransfer.PermitTransferFrom permit; bytes signature; } ``` ### Events 1. **UpdatedRegistration**: This event is emitted when a recipient's registration details are updated. - `recipientId`: The identifier of the recipient whose registration is updated. - `data`: Encoded data containing the recipient's ID, address, and metadata. - `sender`: The address of the sender who initiated the transaction. - `status`: The updated status of the recipient. This event is for tracking changes in recipient registration details and statuses. ```solidity event UpdatedRegistration(address indexed recipientId, bytes data, address sender, uint8 status); ``` 2. **RecipientStatusUpdated**: Triggered when a recipient is registered and their status is updated. - `rowIndex`: The index of the row in the bitmap representing the recipients' statuses. - `fullRow`: The value of the entire row in the bitmap, representing the statuses of multiple recipients. - `sender`: The sender of the transaction. This event is used to log changes in the recipient statuses, providing a transparent record of status updates. ```solidity event RecipientStatusUpdated(uint256 indexed rowIndex, uint256 fullRow, address sender); ``` 3. **TimestampsUpdated**: Emitted when the timestamps for registration and allocation are updated. - `registrationStartTime` and `registrationEndTime`: The start and end times for the registration period. - `allocationStartTime` and `allocationEndTime`: The start and end times for the allocation period. - `sender`: The sender of the transaction. This event indicates changes to critical time periods in the strategy, ensuring that participants are aware of updated timelines. ```solidity event TimestampsUpdated( uint64 registrationStartTime, uint64 registrationEndTime, uint64 allocationStartTime, uint64 allocationEndTime, address sender ); ``` 4. **DistributionUpdated**: Triggered when the distribution details are updated, such as a new Merkle root or metadata. - `merkleRoot`: The Merkle root of the distribution, which is crucial for verifying the integrity of the distribution. - `metadata`: Additional metadata related to the distribution. This event is key for tracking updates to the distribution, especially changes that affect the validation of fund distributions. ```solidity event DistributionUpdated(bytes32 merkleRoot, Metadata metadata); ``` 5. **FundsDistributed**: Emitted when funds are distributed to a recipient. - `amount`: The amount of tokens distributed. - `grantee`: The address of the recipient. - `token`: The address of the token used for distribution. - `recipientId`: The identifier of the recipient. This event is for tracking the actual distribution of funds to recipients. ```solidity event FundsDistributed(uint256 amount, address grantee, address indexed token, address indexed recipientId); ``` 6. **BatchPayoutSuccessful**: Triggered when a batch payout is successfully completed. - `sender`: The sender of the transaction. This event signals the successful completion of a batch payout, an important action in the strategy for efficient fund distribution. ```solidity event BatchPayoutSuccessful(address indexed sender); ``` ### Storage The `DonationVotingMerkleDistributionBaseStrategy` contract includes a section for storage variables, which are essential for maintaining the state and operational data of the contract. Here's a breakdown of these storage variables: 1. **distributionMetadata (Metadata)**: A public variable that stores metadata related to the distribution. ```solidity! Metadata public distributionMetadata; ``` 2. **useRegistryAnchor (bool)**: A boolean flag indicating whether the registry anchor should be used. ```solidity! bool public useRegistryAnchor; ``` 3. **metadataRequired (bool)**: This boolean flag indicates whether metadata is required for certain operations or processes within the contract. ```solidity! bool public metadataRequired; ``` 4. **distributionStarted (bool)**: A flag indicating whether the distribution process has begun. ```solidity! bool public distributionStarted; ``` 5. **registrationStartTime, registrationEndTime, allocationStartTime, allocationEndTime (uint64)**: These are timestamps (in milliseconds) that define the start and end times for registration and allocation periods. ```solidity! uint64 public registrationStartTime; uint64 public registrationEndTime; uint64 public allocationStartTime; uint64 public allocationEndTime; ``` 6. **totalPayoutAmount (uint256)**: This variable represents the total amount of tokens allocated for payout. ```solidity! uint256 public totalPayoutAmount; ``` 7. **recipientsCounter (uint256)**: A counter for the total number of recipients. It starts at 1 and is incremented as new recipients are added. ```solidity! uint256 public recipientsCounter = 1; ``` 8. **_registry (IRegistry)**: A private variable that represents an interface to a registry contract. This is probably used for interactions with an external registry system. ```solidity! IRegistry private _registry; ``` 9. **PERMIT2 (ISignatureTransfer)**: An immutable public variable representing the permit2 interface. This is likely related to permission management for token transfers. ```solidty! ISignatureTransfer public immutable PERMIT2; ``` 10. **merkleRoot (bytes32)**: The Merkle root of the distribution, set by the pool manager. This is crucial for validating the integrity of the distribution through Merkle proofs. ```solidity! bytes32 public merkleRoot; ``` 11. **statusesBitMap (mapping(uint256 => uint256))**: A mapping that acts as a bitmap to store the status of recipients. Each recipient's status is represented by 4 bits, allowing for 5 different statuses. The mapping is pre-allocated with zeros, meaning the default status for any recipient is 'none'. ```solidity! mapping(uint256 => uint256) public statusesBitMap; ``` ``` /// @notice This is a packed array of booleans, 'statuses[0]' is the first row of the bitmap and allows to /// store 256 bits to describe the status of 256 projects. 'statuses[1]' is the second row, and so on /// Instead of using 1 bit for each recipient status, we will use 4 bits for each status /// to allow 5 statuses: /// 0: none /// 1: pending /// 2: accepted /// 3: rejected /// 4: appealed /// Since it's a mapping the storage it's pre-allocated with zero values, so if we check the /// status of an existing recipient, the value is by default 0 (none). /// If we want to check the status of an recipient, we take its index from the `recipients` array /// and convert it to the 2-bits position in the bitmap. ``` 12. **recipientToStatusIndexes (mapping(address => uint256))**: This mapping links recipient IDs to their respective indexes in the `statusesBitMap`. ```solidity! mapping(address => uint256) public recipientToStatusIndexes; ``` 13. **distributedBitMap (mapping(uint256 => uint256))**: A private mapping that serves as a bitmap to track claims that have been distributed. It's structured similarly to the `statusesBitMap`. ```solidity! mapping(uint256 => uint256) private distributedBitMap; ``` 14. **allowedTokens (mapping(address => bool))**: A mapping that tracks which token addresses are allowed for use in the contract. This can be updated by the pool manager and is essential for managing token permissions. ```solidity! mapping(address => bool) public allowedTokens; ``` 15. **_recipients (mapping(address => Recipient))**: An internal mapping that links recipient IDs to their corresponding `Recipient` structs. This is key for tracking individual recipient details. ```solidity! mapping(address => Recipient) internal _recipients; ``` These storage variables collectively maintain the state of the contract and are critical for its various functions, including registration, allocation, distribution, and tracking of recipient statuses and claims. ### Modifier In the `DonationVotingMerkleDistributionBaseStrategy`, there are several modifiers defined to manage and control the access to certain functions based on the status of registration and allocation: 1. **onlyActiveRegistration**: This modifier ensures that the function it modifies can only be executed during the active registration period. It calls the internal function `_checkOnlyActiveRegistration()`, which likely checks that the current block timestamp is between `registrationStartTime` and `registrationEndTime`. If the current time is not within this range, the function will revert and stop execution. This is useful for functions that should only be executed when registration is open. ```solidity! modifier onlyActiveRegistration() { _checkOnlyActiveRegistration(); ``` 2. **onlyActiveAllocation**: Similar to `onlyActiveRegistration`, this modifier restricts the execution of the function to the active allocation period. The internal function `_checkOnlyActiveAllocation()` checks that the current time is between `allocationStartTime` and `allocationEndTime`. If the check fails (i.e., the current time is outside this range), the function execution is halted. This modifier is essential for functions that are pertinent only during the allocation phase. ```solidity! modifier onlyActiveAllocation() { _checkOnlyActiveAllocation(); ``` 3. **onlyAfterAllocation**: This modifier is used to restrict function execution to after the allocation period has ended. The `_checkOnlyAfterAllocation()` function likely checks if the current time is greater than `allocationEndTime`. If the current time is still within or before the allocation period, the function will revert. ```solidity! modifier onlyAfterAllocation() { _checkOnlyAfterAllocation(); ``` 4. **onlyBeforeAllocationEnds**: Contrary to `onlyAfterAllocation`, this modifier ensures that the decorated function can only be called before the allocation period ends. The `_checkOnlyBeforeAllocationEnds()` function probably verifies that the current time is less than `allocationEndTime`. If the current time is after the end of the allocation period, the function will revert. This is useful for actions that must be completed before the allocation period concludes. ```solidity! modifier onlyBeforeAllocationEnds() { _checkOnlyBeforeAllocationEnds(); ``` Each of these modifiers serves to enforce certain temporal conditions on function execution, ensuring that functions are called at appropriate times in relation to the registration and allocation periods. This adds a layer of control and security to the contract's operations, aligning them with the intended workflow of the donation and allocation process. ### Constructor ```solidity! constructor(address _allo, string memory _name, ISignatureTransfer _permit2) BaseStrategy(_allo, _name) { if (address(_permit2) == address(0)) revert ZERO_ADDRESS(); PERMIT2 = _permit2; } ``` 1. **Parameters**: - `_allo`: This parameter is the address of the Allo contract that manages the overall logic or data. - `_name`: A string parameter representing the name of the strategy. This could be used for identification or display purposes within the Allo ecosystem. - `_permit2`: This is an instance of the `ISignatureTransfer` interface. It's likely used to manage signature-based token transfers, a common feature in DeFi protocols for efficient and secure token management. 2. **Inheritance**: - `BaseStrategy(_allo, _name)`: The constructor calls the constructor of the `BaseStrategy` contract, passing `_allo` and `_name` as arguments. This suggests that `DonationVotingMerkleDistributionBaseStrategy` inherits from `BaseStrategy`, and this line initializes the inherited aspects of the contract. 3. **Zero Address Check**: - `if (address(_permit2) == address(0)) revert ZERO_ADDRESS();`: This line checks if the provided `_permit2` address is the zero address (an address with all zeros, representing an empty or uninitialized address in Ethereum). If it is, the contract deployment is reverted to prevent initialization with an invalid address. This is a standard safety check in smart contract development to ensure that all parameters are properly initialized. 4. **Setting `PERMIT2`**: - `PERMIT2 = _permit2;`: This line assigns the `_permit2` parameter to the `PERMIT2` state variable of the contract. This ensures that the `PERMIT2` interface is available throughout the contract for managing signature-based token transfers. The constructor of this contract sets up the initial state by initializing inherited properties from `BaseStrategy`, performing a safety check on the `_permit2` parameter, and then assigning `_permit2` to a state variable for use throughout the contract. This is a typical pattern for setting up a smart contract's initial state and dependencies in Solidity. ### Initialize #### `initialize` Function ```solidity! function initialize(uint256 _poolId, bytes memory _data) external virtual override onlyAllo { (InitializeData memory initializeData) = abi.decode(_data, (InitializeData)); __DonationVotingStrategy_init(_poolId, initializeData); emit Initialized(_poolId, _data); } ``` - **Purpose**: To initialize the strategy with given parameters. - **Parameters**: - `_poolId`: A unique identifier for the pool to which this strategy is being applied. - `_data`: Encoded data that is expected to be decoded into the `InitializeData` struct. - **Functionality**: 1. **Data Decoding**: The function first decodes the `_data` parameter into an `InitializeData` struct, which contains various configuration settings. 2. **Internal Initialization Call**: It then calls the `__DonationVotingStrategy_init` internal function, passing the `_poolId` and the decoded `initializeData`. 3. **Event Emission**: Finally, it emits an `Initialized` event with the `_poolId` and `_data` to signal that the initialization has occurred. - **Access Control**: The function is marked `external` and `onlyAllo`, meaning it can only be called from outside the contract and specifically by the Allo contract. This ensures controlled and secure initialization. #### `__DonationVotingStrategy_init` Internal Function ```solidity! function __DonationVotingStrategy_init(uint256 _poolId, InitializeData memory _initializeData) internal { __BaseStrategy_init(_poolId); useRegistryAnchor = _initializeData.useRegistryAnchor; metadataRequired = _initializeData.metadataRequired; _registry = allo.getRegistry(); registrationStartTime = _initializeData.registrationStartTime; registrationEndTime = _initializeData.registrationEndTime; allocationStartTime = _initializeData.allocationStartTime; allocationEndTime = _initializeData.allocationEndTime; _isPoolTimestampValid(registrationStartTime, registrationEndTime, allocationStartTime, allocationEndTime); emit TimestampsUpdated( registrationStartTime, registrationEndTime, allocationStartTime, allocationEndTime, msg.sender ); uint256 allowedTokensLength = _initializeData.allowedTokens.length; if (allowedTokensLength == 0) { // all tokens allowedTokens[address(0)] = true; } for (uint256 i; i < allowedTokensLength;) { allowedTokens[_initializeData.allowedTokens[i]] = true; unchecked { i++; } } } ``` - **Purpose**: To conduct the actual initialization process, setting up various state variables. - **Functionality**: 1. **Base Strategy Initialization**: Initializes the base strategy with the provided `_poolId`. 2. **State Variable Setup**: Sets up various state variables like `useRegistryAnchor`, `metadataRequired`, and the `_registry` address from the Allo contract. 3. **Timestamp Setup**: Assigns the start and end times for registration and allocation from the provided `InitializeData`. 4. **Timestamp Validation**: Checks if the provided timestamps are valid. This validation is not detailed here, but it likely ensures the timestamps are in a logical order and within acceptable ranges. 5. **Emit TimestampsUpdated Event**: Emits an event with the new timestamps and the sender's address. 6. **Allowed Tokens Configuration**: Sets up the `allowedTokens` mapping. If the length of `allowedTokens` in `InitializeData` is zero, it allows all tokens by setting `allowedTokens[address(0)]` to true. Otherwise, it iterates through the array and marks each token as allowed. - **Loop Optimization**: The loop to set `allowedTokens` uses `unchecked` to optimize gas usage, as Solidity 0.8 and above revert on overflow by default. ```solidity! // Loop through the allowed tokens and set them to true for (uint256 i; i < allowedTokensLength;) { allowedTokens[_initializeData.allowedTokens[i]] = true; unchecked { i++; ``` These functions collectively handle the initialization of the strategy, setting up crucial parameters such as registry settings, timestamps for different phases, and allowed tokens for transactions. ### Views The "Views" section of the `DonationVotingMerkleDistributionBaseStrategy` contract comprises functions designed to retrieve specific information from the contract's state without modifying it. These are read-only functions and do not incur any gas costs when called externally. Here's an overview of the two view functions provided in this section: ### `getRecipient` Function ```solidity! function getRecipient(address _recipientId) external view returns (Recipient memory recipient) { return _getRecipient(_recipientId); } ``` - **Purpose**: To retrieve the details of a specific recipient by their ID. - **Parameter**: - `_recipientId`: The unique identifier (address) of the recipient whose details are being requested. - **Return**: - `recipient`: The function returns a `Recipient` struct, which contains details about the recipient. The exact structure of `Recipient` is not detailed here, but it likely includes information such as whether to use a registry anchor, the recipient's address, and associated metadata. - **Implementation**: - The function internally calls `_getRecipient`, passing `_recipientId`, and returns its result. This suggests a pattern of having a public external view function that wraps around an internal function for modularity and reusability. ### `_getRecipientStatus` Function ```solidity! function _getRecipientStatus(address _recipientId) internal view override returns (Status) { return Status(_getUintRecipientStatus(_recipientId)); ``` - **Purpose**: To get the status of a specific recipient. - **Parameter**: - `_recipientId`: The address ID of the recipient whose status is being queried. - **Return**: - The function returns a `Status` value for the recipient. The `Status` type is not defined in the provided snippet, but it's likely an enumerated type representing different possible states of a recipient in the strategy, such as pending, accepted, or rejected. - **Implementation**: - This function is marked as `internal` and `view`, implying it can only be called from within the contract or its inheritors and does not modify the contract state. - The function returns the result of `_getUintRecipientStatus`, converted to a `Status` type. This suggests that the underlying status is stored as an unsigned integer, which is then mapped to a more readable `Status` type for external understanding. ### External Custom The "External/Custom" section of the `DonationVotingMerkleDistributionBaseStrategy` contract contains functions that are primarily designed for external access and serve specific operational needs of the contract. These functions facilitate key activities such as managing recipient statuses, updating important timestamps, and handling fund withdrawals. #### `reviewRecipients` Function ```solidity! function reviewRecipients(ApplicationStatus[] memory statuses, uint256 refRecipientsCounter) external onlyBeforeAllocationEnds onlyPoolManager(msg.sender) { if (refRecipientsCounter != recipientsCounter) revert INVALID(); // Loop through the statuses and set the status for (uint256 i; i < statuses.length;) { uint256 rowIndex = statuses[i].index; uint256 fullRow = statuses[i].statusRow; statusesBitMap[rowIndex] = fullRow; // Emit that the recipient status has been updated with the values emit RecipientStatusUpdated(rowIndex, fullRow, msg.sender); unchecked { i++; } } } ``` - **Purpose**: To set the statuses of recipients. - **Parameters**: - `statuses`: An array of `ApplicationStatus` structs, each representing the status of a recipient. - `refRecipientsCounter`: A reference to the `recipientsCounter` at the time the transaction is based on. - **Access Control**: - Restricted to be called by a pool manager and only before the allocation ends (`onlyBeforeAllocationEnds` and `onlyPoolManager` modifiers). - **Functionality**: 1. **Validation**: Checks if the provided `refRecipientsCounter` matches the current `recipientsCounter`. If not, the transaction is reverted. 2. **Status Update**: Iterates through each `ApplicationStatus` in the provided array, updating the `statusesBitMap` with the new status. 3. **Event Emission**: Emits a `RecipientStatusUpdated` event for each status update. - **Remarks**: This function is crucial for updating and tracking the status of recipients in a scalable way using a bitmap. #### `updatePoolTimestamps` Function ```solidity! function updatePoolTimestamps( uint64 _registrationStartTime, uint64 _registrationEndTime, uint64 _allocationStartTime, uint64 _allocationEndTime ) external onlyPoolManager(msg.sender) { // If the timestamps are invalid this will revert - See details in '_isPoolTimestampValid' _isPoolTimestampValid(_registrationStartTime, _registrationEndTime, _allocationStartTime, _allocationEndTime); // Set the updated timestamps registrationStartTime = _registrationStartTime; registrationEndTime = _registrationEndTime; allocationStartTime = _allocationStartTime; allocationEndTime = _allocationEndTime; // Emit that the timestamps have been updated with the updated values emit TimestampsUpdated( registrationStartTime, registrationEndTime, allocationStartTime, allocationEndTime, msg.sender ); } ``` - **Purpose**: To update the start and end times for registration and allocation. - **Parameters**: - `_registrationStartTime`, `_registrationEndTime`, `_allocationStartTime`, `_allocationEndTime`: Timestamps in milliseconds for the respective start and end times. - **Access Control**: - Can only be called by a pool manager (`onlyPoolManager` modifier). - **Functionality**: 1. **Validation**: Checks the validity of the provided timestamps. 2. **Timestamp Update**: Updates the contract's state with the new timestamps. 3. **Event Emission**: Emits a `TimestampsUpdated` event. - **Remarks**: This function is key for adjusting the time-sensitive phases of the pool's operation, ensuring flexibility and adaptability. #### `withdraw` Function ```solidity! function withdraw(address _token) external onlyPoolManager(msg.sender) { if (block.timestamp <= allocationEndTime + 30 days) { revert INVALID(); } // get the actual balance hold by the pool uint256 amount = _getBalance(_token, address(this)); // get the token amount in vault which belong to the recipients uint256 tokenInVault = _tokenAmountInVault(_token); // calculate the amount which is accessible uint256 accessableAmount = amount - tokenInVault; // transfer the amount to the pool manager _transferAmount(_token, msg.sender, accessableAmount); } ``` - **Purpose**: To withdraw funds from the pool. - **Parameters**: - `_token`: The address of the token to be withdrawn. - **Access Control**: - Only callable by a pool manager and after the allocation has ended plus an additional 30 days (`onlyPoolManager` modifier and time check). - **Functionality**: 1. **Time Check**: Ensures that the current time is at least 30 days past the allocation end time. 2. **Fund Calculation**: Calculates the accessible amount by considering the balance held by the pool and the amount locked in the vault. 3. **Fund Transfer**: Transfers the accessible amount to the pool manager. - **Remarks**: This function manages the withdrawal of funds in a secure manner, ensuring that it happens only after certain conditions are met. #### `_tokenAmountInVault` Internal View Function ```solidity! function _tokenAmountInVault(address _token) internal view virtual returns (uint256); ``` - **Purpose**: To return the amount of a specific token that is locked in the vault. - **Parameters**: - `_token`: The address of the token. - **Functionality**: - Returns the amount of the specified token that is currently locked in the vault. - **Remarks**: As an internal view function, it is a helper function used by other functions like `withdraw` to calculate the accessible amount of funds. The "Merkle" section of the contract includes functions related to managing and querying the Merkle root and related distribution metadata. #### `updateDistribution` Function ```solidity! function updateDistribution(bytes32 _merkleRoot, Metadata memory _distributionMetadata) external onlyAfterAllocation onlyPoolManager(msg.sender) ``` - **Purpose**: To update the Merkle root and distribution metadata for the current round. - **Parameters**: - `_merkleRoot`: The new Merkle root of the distribution. - `_distributionMetadata`: The metadata associated with the distribution. - **Access Control**: - Can only be called by a pool manager and after the allocation phase has ended (`onlyAfterAllocation` and `onlyPoolManager` modifiers). - **Functionality**: 1. **Validation**: Checks if the distribution has already started. If so, it reverts the transaction to prevent changes to an active distribution. 2. **Updating State Variables**: Assigns the provided `_merkleRoot` and `_distributionMetadata` to the respective state variables of the contract. 3. **Event Emission**: Emits a `DistributionUpdated` event with the updated Merkle root and distribution metadata. - **Remarks**: This function is critical for updating the details of the fund distribution, ensuring that the process aligns with the latest distribution plan and data. #### `isDistributionSet` Function ```solidity! function isDistributionSet() external view returns (bool) { return merkleRoot != ""; } ``` - **Purpose**: To check if the distribution has been set (i.e., if a Merkle root has been established). - **Return**: - Returns `true` if the distribution is set (merkleRoot is not empty), otherwise returns `false`. - **Implementation**: - The function simply checks whether the `merkleRoot` is not equal to an empty string, indicating whether the distribution has been initialized with a Merkle root. #### `hasBeenDistributed` Function ```solidity! function hasBeenDistributed(uint256 _index) external view returns (bool) { return _hasBeenDistributed(_index); } ``` - **Purpose**: To check if a particular distribution, identified by its index, has been completed. - **Parameters**: - `_index`: The index of the distribution to check. - **Return**: - Returns `true` if the distribution at the given index is completed, otherwise `false`. - **Implementation**: - This function is an external view function that internally calls `_hasBeenDistributed`, passing the `_index`. This internal function likely checks against some form of distribution tracking to determine if the distribution for the given index is completed. ### Internal The "Internal" section of the `DonationVotingMerkleDistributionBaseStrategy` contract contains various internal functions that are crucial for the contract's operation. These functions are not accessible externally but are used by other functions within the contract to perform specific tasks. Here's a breakdown of the internal functions in the provided snippet: --- #### `_checkOnlyActiveRegistration` ```solidity! function _checkOnlyActiveRegistration() internal view { if (registrationStartTime > block.timestamp || block.timestamp > registrationEndTime) { revert REGISTRATION_NOT_ACTIVE(); ``` - **Purpose**: To ensure that the current block timestamp is within the active registration period. - **Functionality**: Checks if the current timestamp is greater than `registrationStartTime` and less than `registrationEndTime`. If not, it reverts the transaction with a `REGISTRATION_NOT_ACTIVE` error. --- #### `_checkOnlyActiveAllocation` ```solidity! function _checkOnlyActiveAllocation() internal view { if (allocationStartTime > block.timestamp || block.timestamp > allocationEndTime) { revert ALLOCATION_NOT_ACTIVE(); ``` - **Purpose**: To ensure that the current block timestamp is within the active allocation period. - **Functionality**: Similar to `_checkOnlyActiveRegistration`, but for the allocation period. It reverts the transaction if the current timestamp is not within the allocation period. --- #### `_checkOnlyAfterAllocation` ```solidity! function _checkOnlyAfterAllocation() internal view { if (block.timestamp <= allocationEndTime) { revert ALLOCATION_NOT_ENDED(); ``` - **Purpose**: To ensure that the allocation period has ended. - **Functionality**: Checks if the current timestamp is after `allocationEndTime`. If not, it reverts with an `ALLOCATION_NOT_ENDED` error. --- #### `_checkOnlyBeforeAllocationEnds` ```solidity! function _checkOnlyBeforeAllocationEnds() internal view { if (block.timestamp > allocationEndTime) { revert ALLOCATION_NOT_ACTIVE(); ``` - **Purpose**: To ensure that the allocation period has not yet ended. - **Functionality**: Checks if the current timestamp is before `allocationEndTime`. If not, it reverts with an `ALLOCATION_NOT_ACTIVE` error. --- #### `_isValidAllocator` ```solidity! function _isValidAllocator(address) internal pure override returns (bool) { return true; ``` - **Purpose**: To check if a given address is an eligible allocator. - **Functionality**: Always returns `true`, indicating that in this strategy, any address can be considered a valid allocator. --- #### `_isPoolTimestampValid` ```solidity! function _isPoolTimestampValid( uint64 _registrationStartTime, uint64 _registrationEndTime, uint64 _allocationStartTime, uint64 _allocationEndTime ) internal view { if ( block.timestamp > _registrationStartTime || _registrationStartTime > _registrationEndTime || _registrationStartTime > _allocationStartTime || _allocationStartTime > _allocationEndTime || _registrationEndTime > _allocationEndTime ) { revert INVALID(); ``` - **Purpose**: To validate the pool's timestamps. - **Functionality**: Checks several conditions to ensure the validity of the provided timestamps for registration and allocation. It reverts with an `INVALID` error if any condition is not met. --- #### `_isPoolActive` ```solidity! function _isPoolActive() internal view override returns (bool) { if (registrationStartTime <= block.timestamp && block.timestamp <= registrationEndTime) { return true; } return false; ``` - **Purpose**: To check if the pool is currently active. - **Functionality**: Returns `true` if the current timestamp is within the registration period, otherwise returns `false`. --- #### `_registerRecipient` ```solidity! function _registerRecipient(bytes memory _data, address _sender) internal override onlyActiveRegistration returns (address recipientId) { bool isUsingRegistryAnchor; address recipientAddress; address registryAnchor; Metadata memory metadata; // decode data custom to this strategy if (useRegistryAnchor) { (recipientId, recipientAddress, metadata) = abi.decode(_data, (address, address, Metadata)); // If the sender is not a profile member this will revert if (!_isProfileMember(recipientId, _sender)) { revert UNAUTHORIZED(); } } else { (recipientAddress, registryAnchor, metadata) = abi.decode(_data, (address, address, Metadata)); // Set this to 'true' if the registry anchor is not the zero address isUsingRegistryAnchor = registryAnchor != address(0); // If using the 'registryAnchor' we set the 'recipientId' to the 'registryAnchor', otherwise we set it to the 'msg.sender' recipientId = isUsingRegistryAnchor ? registryAnchor : _sender; // Checks if the '_sender' is a member of the profile 'anchor' being used and reverts if not if (isUsingRegistryAnchor && !_isProfileMember(recipientId, _sender)) { revert UNAUTHORIZED(); } } // If the metadata is required and the metadata is invalid this will revert if (metadataRequired && (bytes(metadata.pointer).length == 0 || metadata.protocol == 0)) { revert INVALID_METADATA(); } // If the recipient address is the zero address this will revert if (recipientAddress == address(0)) { revert RECIPIENT_ERROR(recipientId); } // Get the recipient Recipient storage recipient = _recipients[recipientId]; // update the recipients data recipient.recipientAddress = recipientAddress; recipient.metadata = metadata; recipient.useRegistryAnchor = useRegistryAnchor ? true : isUsingRegistryAnchor; if (recipientToStatusIndexes[recipientId] == 0) { // recipient registering new application recipientToStatusIndexes[recipientId] = recipientsCounter; _setRecipientStatus(recipientId, uint8(Status.Pending)); bytes memory extendedData = abi.encode(_data, recipientsCounter); emit Registered(recipientId, extendedData, _sender); recipientsCounter++; } else { uint8 currentStatus = _getUintRecipientStatus(recipientId); if (currentStatus == uint8(Status.Accepted)) { // recipient updating accepted application _setRecipientStatus(recipientId, uint8(Status.Pending)); } else if (currentStatus == uint8(Status.Rejected)) { // recipient updating rejected application _setRecipientStatus(recipientId, uint8(Status.Appealed)); } emit UpdatedRegistration(recipientId, _data, _sender, _getUintRecipientStatus(recipientId)); ``` - **Purpose**: To submit a recipient to the pool and set their status. - **Parameters**: - `_data`: Encoded data that varies based on whether `useRegistryAnchor` is `true` or `false`. - `_sender`: The address of the sender of the transaction. - **Functionality**: 1. Decodes the provided `_data` into either a pair or trio of variables, including recipient address and metadata. 2. Performs various checks, including sender authorization, metadata validity, and recipient address validity. 3. Updates or registers the recipient's information in the `_recipients` mapping. 4. Sets the recipient's status and emits appropriate events (`Registered` or `UpdatedRegistration`). --- #### `_distribute` ```solidity! function _distribute(address[] memory, bytes memory _data, address _sender) internal virtual override onlyPoolManager(_sender) { if (merkleRoot == "") revert INVALID(); if (!distributionStarted) { distributionStarted = true; } // Decode the '_data' to get the distributions Distribution[] memory distributions = abi.decode(_data, (Distribution[])); uint256 length = distributions.length; // Loop through the distributions and distribute the funds for (uint256 i; i < length;) { _distributeSingle(distributions[i]); unchecked { i++; } } // Emit that the batch payout was successful emit BatchPayoutSuccessful(_sender); ``` - **Purpose**: Distribute funds to recipients as per the provided distributions. - **Parameters**: - `_data`: Encoded data containing an array of `Distribution` structs. - `_sender`: The sender of the transaction. - **Access Control**: Only callable by the pool manager. - **Functionality**: 1. **Validation**: Checks if the `merkleRoot` is set and if `distributionStarted` is false, then sets it to true. 2. **Distribution Loop**: Decodes `_data` to get an array of `Distribution` and loops through them, calling `_distributeSingle` for each distribution. 3. **Event Emission**: Emits a `BatchPayoutSuccessful` event after successful distribution. --- #### `_allocate` ```solidity! function _allocate(bytes memory _data, address _sender) internal virtual override onlyActiveAllocation { // Decode the '_data' to get the recipientId, amount and token (address recipientId, Permit2Data memory p2Data) = abi.decode(_data, (address, Permit2Data)); uint256 amount = p2Data.permit.permitted.amount; address token = p2Data.permit.permitted.token; // If the recipient status is not 'Accepted' this will revert, the recipient must be accepted through registration if (Status(_getUintRecipientStatus(recipientId)) != Status.Accepted) { revert RECIPIENT_ERROR(recipientId); } // The token must be in the allowed token list and not be native token or zero address if (!allowedTokens[token] && !allowedTokens[address(0)]) { revert INVALID(); } // If the token is native, the amount must be equal to the value sent, otherwise it reverts if (msg.value > 0 && token != NATIVE || token == NATIVE && msg.value != amount) { revert INVALID(); } // Emit that the amount has been allocated to the recipient by the sender emit Allocated(recipientId, amount, token, _sender); }function _allocate(bytes memory _data, address _sender) internal virtual override onlyActiveAllocation { // Decode the '_data' to get the recipientId, amount and token (address recipientId, Permit2Data memory p2Data) = abi.decode(_data, (address, Permit2Data)); uint256 amount = p2Data.permit.permitted.amount; address token = p2Data.permit.permitted.token; // If the recipient status is not 'Accepted' this will revert, the recipient must be accepted through registration if (Status(_getUintRecipientStatus(recipientId)) != Status.Accepted) { revert RECIPIENT_ERROR(recipientId); } // The token must be in the allowed token list and not be native token or zero address if (!allowedTokens[token] && !allowedTokens[address(0)]) { revert INVALID(); } // If the token is native, the amount must be equal to the value sent, otherwise it reverts if (msg.value > 0 && token != NATIVE || token == NATIVE && msg.value != amount) { revert INVALID(); } // Emit that the amount has been allocated to the recipient by the sender emit Allocated(recipientId, amount, token, _sender); } ``` - **Purpose**: Allocate tokens to a recipient during the allocation period. - **Parameters**: - `_data`: Encoded data containing the recipient's ID and a `Permit2Data` struct. - `_sender`: The sender of the transaction. - **Functionality**: 1. **Data Decoding**: Decodes `_data` to extract recipient ID, amount, and token. 2. **Validation**: Checks recipient status, token allowance, and value correctness. 3. **Event Emission**: Emits an `Allocated` event signaling successful allocation. --- #### `_isProfileMember` ```solidity! function _isProfileMember(address _anchor, address _sender) internal view virtual returns (bool) { IRegistry.Profile memory profile = _registry.getProfileByAnchor(_anchor); return _registry.isOwnerOrMemberOfProfile(profile.id, _sender); } ``` - **Purpose**: Check if a sender is a profile owner or member of a given profile. - **Parameters**: - `_anchor`: The anchor of the profile. - `_sender`: The sender of the transaction. - **Return**: Returns `true` if the sender is a member or owner of the profile, otherwise `false`. - **Functionality**: Uses the `_registry` contract to check if the sender is associated with the given profile anchor. --- #### `_getRecipient` ```solidity! function _getRecipient(address _recipientId) internal view returns (Recipient memory) { return _recipients[_recipientId]; } ``` - **Purpose**: Retrieve recipient details. - **Parameters**: - `_recipientId`: The ID of the recipient. - **Return**: The `Recipient` struct containing recipient details. - **Functionality**: Returns the recipient details from the `_recipients` mapping using the given ID. --- #### `_getPayout` ```solidity! function _getPayout(address, bytes memory _data) internal view override returns (PayoutSummary memory) { // Decode the '_data' to get the distribution Distribution memory distribution = abi.decode(_data, (Distribution)); uint256 index = distribution.index; address recipientId = distribution.recipientId; uint256 amount = distribution.amount; bytes32[] memory merkleProof = distribution.merkleProof; address recipientAddress = _getRecipient(recipientId).recipientAddress; ``` - **Purpose**: Return the payout summary for an accepted recipient. - **Parameters**: - `_data`: Encoded data containing a `Distribution` struct. - **Return**: A `PayoutSummary` struct with recipient address and amount. - **Functionality**: 1. **Data Decoding**: Decodes `_data` to get a `Distribution`. 2. **Validation**: Validates the distribution against the Merkle root and other criteria. 3. **Summary Creation**: Returns a `PayoutSummary` with the recipient's address and amount if valid, or with an amount of 0 if not valid. --- #### `_validateDistribution` ```solidity! function _validateDistribution( uint256 _index, address _recipientId, address _recipientAddress, uint256 _amount, bytes32[] memory _merkleProof ) internal view returns (bool) { // If the '_index' has been distributed this will return 'false' if (_hasBeenDistributed(_index)) { return false; } // Generate the node that will be verified in the 'merkleRoot' bytes32 node = keccak256(bytes.concat(keccak256(abi.encode(_index, _recipientId, _recipientAddress, _amount)))); // If the node is not verified in the 'merkleRoot' this will return 'false' if (!MerkleProof.verify(_merkleProof, merkleRoot, node)) { return false; } // Return 'true', the distribution is valid at this point return true; } ``` - **Purpose**: To validate a distribution using a Merkle proof. - **Parameters**: Index, recipient ID and address, amount, and Merkle proof. - **Functionality**: Checks if the distribution has already been made and verifies the provided data against the Merkle root. - **Return**: Returns `true` if the distribution is valid, `false` otherwise. --- #### `_hasBeenDistributed` ```solidity! function _hasBeenDistributed(uint256 _index) internal view returns (bool) { // Get the word index by dividing the '_index' by 256 uint256 distributedWordIndex = _index / 256; // Get the bit index by getting the remainder of the '_index' divided by 256 uint256 distributedBitIndex = _index % 256; // Get the word from the 'distributedBitMap' using the 'distributedWordIndex' uint256 distributedWord = distributedBitMap[distributedWordIndex]; // Get the mask by shifting 1 to the left of the 'distributedBitIndex' uint256 mask = (1 << distributedBitIndex); // Return 'true' if the 'distributedWord' and 'mask' are equal to the 'mask' return distributedWord & mask == mask; } ``` - **Purpose**: To check if a particular distribution has been completed. - **Functionality**: Uses bitwise operations on the `distributedBitMap` to determine if a distribution at a specific index has been made. - **Return**: Returns `true` if the distribution has been completed, `false` otherwise. --- #### `_setDistributed` ```solidity! function _setDistributed(uint256 _index) private { // Get the word index by dividing the '_index' by 256 uint256 distributedWordIndex = _index / 256; // Get the bit index by getting the remainder of the '_index' divided by 256 uint256 distributedBitIndex = _index % 256; // Set the bit in the 'distributedBitMap' shifting 1 to the left of the 'distributedBitIndex' distributedBitMap[distributedWordIndex] |= (1 << distributedBitIndex); } ``` - **Purpose**: To mark a distribution as completed. - **Functionality**: Updates the `distributedBitMap` to indicate that a distribution at a given index is done. --- #### `_distributeSingle` ```solidity! function _setDistributed(uint256 _index) private { // Get the word index by dividing the '_index' by 256 uint256 distributedWordIndex = _index / 256; // Get the bit index by getting the remainder of the '_index' divided by 256 uint256 distributedBitIndex = _index % 256; // Set the bit in the 'distributedBitMap' shifting 1 to the left of the 'distributedBitIndex' distributedBitMap[distributedWordIndex] |= (1 << distributedBitIndex); } ``` - **Purpose**: To distribute funds to a single recipient. - **Parameters**: A `Distribution` struct. - **Functionality**: 1. Validates the distribution. 2. If valid, transfers the specified amount to the recipient and updates the pool amount. 3. Emits a `FundsDistributed` event. 4. Reverts if the distribution is not valid. --- #### `_setRecipientStatus` ```solidity! function _setRecipientStatus(address _recipientId, uint256 _status) internal { // Get the row index, column index and current row (uint256 rowIndex, uint256 colIndex, uint256 currentRow) = _getStatusRowColumn(_recipientId); // Calculate the 'newRow' uint256 newRow = currentRow & ~(15 << colIndex); // Add the status to the mapping statusesBitMap[rowIndex] = newRow | (_status << colIndex); } ``` - **Purpose**: To set the status of a recipient. - **Parameters**: Recipient ID and status. - **Functionality**: Updates the recipient's status in the `statusesBitMap`. --- #### `_getUintRecipientStatus` ```solidity! function _getUintRecipientStatus(address _recipientId) internal view returns (uint8 status) { if (recipientToStatusIndexes[_recipientId] == 0) return 0; // Get the column index and current row (, uint256 colIndex, uint256 currentRow) = _getStatusRowColumn(_recipientId); // Get the status from the 'currentRow' shifting by the 'colIndex' status = uint8((currentRow >> colIndex) & 15); // Return the status return status; } ``` - **Purpose**: To retrieve the status of a recipient. - **Parameters**: Recipient ID. - **Return**: The status of the recipient. - **Functionality**: Extracts the recipient's status from the `statusesBitMap`. --- #### `_getStatusRowColumn` ```solidity! function _getStatusRowColumn(address _recipientId) internal view returns (uint256, uint256, uint256) { uint256 recipientIndex = recipientToStatusIndexes[_recipientId] - 1; uint256 rowIndex = recipientIndex / 64; // 256 / 4 uint256 colIndex = (recipientIndex % 64) * 4; return (rowIndex, colIndex, statusesBitMap[rowIndex]); } ``` - **Purpose**: To get the row and column indices in the `statusesBitMap` for a recipient's status. - **Parameters**: Recipient ID. - **Return**: Row index, column index, and current row value in the `statusesBitMap`. - **Functionality**: Calculates the indices for accessing the recipient's status in the bitmap. --- #### `receive` ```solidity! receive() external payable {} ``` - **Purpose**: To enable the contract to receive native tokens (like ETH). - **Functionality**: A special function in Solidity that allows the contract to accept native token transfers. --- ### Key Aspects #### Structs The strategy uses several structs to organize data, including `ApplicationStatus`, `Recipient`, `Distribution`, `InitializeData`, `Permit2Data`. These structs serve to store data ranging from application status and recipient details to distribution specifics and initialization parameters. #### Events The strategy defines a series of events for logging significant actions: `UpdatedRegistration`, `RecipientStatusUpdated`, `TimestampsUpdated`, `DistributionUpdated`, `FundsDistributed`, `BatchPayoutSuccessful`. These events provide transparency and auditability for actions such as registration updates, status changes, timestamp modifications, distribution updates, and fund distributions. #### Storage Variables Key storage variables include flags for registry anchor usage, metadata requirement, and distribution start; timestamps for registration and allocation; total payout amount; recipient count; and mappings for registry interfaces, permitted tokens, recipient details, status bitmaps, and distributed bitmaps. These variables maintain the state and operational data of the strategy. #### Modifiers Modifiers such as `onlyActiveRegistration`, `onlyActiveAllocation`, `onlyAfterAllocation`, and `onlyBeforeAllocationEnds` enforce temporal restrictions on certain functions, ensuring they are executed at appropriate times in relation to the registration and allocation periods. #### Constructor The constructor initializes the strategy with parameters like the Allo contract address, strategy name, and the permit2 interface, setting up the necessary state and dependencies. #### Initialize Function The `initialize` function sets up the strategy with initial parameters and rules for operation, defining aspects like registry anchor usage, metadata requirements, registration, and allocation periods, as well as allowed tokens. #### Views Functions View functions `getRecipient` and `_getRecipientStatus` provide read-only access to recipient details and statuses, facilitating transparency and easy data retrieval. #### External/Custom Functions Functions like `reviewRecipients`, `updatePoolTimestamps`, and `withdraw` handle recipient status updates, timestamp modifications, and fund withdrawals, respectively. They are crucial for the operational management of the strategy. #### Merkle-Related Functions Merkle-related functions like `updateDistribution`, `isDistributionSet`, `hasBeenDistributed`, and `_distribute` manage the distribution details, validate distributions using Merkle proofs, and handle the actual distribution of funds. #### Internal Functions A set of internal functions perform critical operations like checking registration and allocation statuses, validating allocators and timestamps, registering and managing recipients, distributing funds, and managing recipient statuses. #### Summary The Allo Merkle Donation Base Strategy is a comprehensive framework for managing fund distributions within the Allo Protocol. It emphasizes security, transparency, and efficiency, leveraging Merkle proofs for distribution validation and employing a range of checks and balances to ensure accurate and fair fund allocation. The strategy's design reflects a deep integration of smart contract best practices, ensuring robustness and reliability in handling capital allocation and distribution for various use cases.