### Objective Objective of this review is to figure out if there is any security loophole in the contract and if it adheres to the requirement as is expected ### Requirements - An owner should be able to distribute token rewards through a merkle distributor - This should be interfaced via a periphery, such that users need to call only one function instead of calling multiple claims - The owner should be able to withdraw post deadline in case there is still funds in the distributor ### Actors - Owner - the address that owns the periphery by extension the owner of the merkle distributors. They can call `create`, `withdraw`, `claim` except the view functions - Users - the addresses that can claim the amounts in the distributors through the periphery. They can call `claim` execpt the view functions ### Dependencies - **OpenZeppelin Contracts**: - `IERC20`: Interface for ERC20 standard. - `SafeERC20`: Library for safe ERC20 operations. - `Ownable`: Contract module for single owner authorization. - `Ownable2Step`: Extension of `Ownable` for two-step ownership transfer. - `Create2`: Library for deterministic contract deployment. - `IOwnable2Step`: Extended ownership management interface. - **Uniswap Interfaces and Contracts**: - `IMerkleDistributorWithDeadline`: Interface for Merkle Distributors with deadlines. - `MerkleDistributorWithDeadline`: Implementation of a Merkle Distributor with a deadline. - Merkle Distributor Periphery Interface - `IMerkleDistributorPeriphery`: Interface for the Merkle Distributor Periphery contract. - **Custom Types** - `Amount`: Type for handling amounts. - `Id`: Type for handling IDs. - `Index`: Type for handling indices. - `MerkleProof`: Type for handling Merkle Proofs. - `MerkleRoot`: Type for handling Merkle Roots. - `Number`: Type for handling numerical values. - `Timestamp`: Type for handling timestamps. ### Contract Structure ![Screenshot 2023-11-23 at 2.24.42 PM](https://hackmd.io/_uploads/BJ4v7q34a.png) 1. **State Variables**: - `storedDistributors`: Array storing `IMerkleDistributorWithDeadline` contract addresses. 2. **Constructor**: - Initializes the contract, setting the deployer as the owner. - Documentation correct? 1. **Functions**: - **View Functions**: - `merkleDistributor(Id id)`, `totalMerkleDistributors()`, `areClaimed(Query[] calldata queries)` - **Transaction Functions**: - `create(IERC20 token, Amount totalAmount, MerkleRoot merkleRoot, Timestamp endTime)`, `claim(Order[] calldata orders)`, `withdraw(IMerkleDistributorWithDeadline[] calldata distributors)` - **Ownership Functions** (Overridden from `Ownable` and `Ownable2Step`): - `owner()`, `pendingOwner()`, `transferOwnership(address newOwner)`, `acceptOwnership()`, `renounceOwnership()` 2. **Modifiers**: - `onlyOwner`: Restricts function access to the contract owner. - Note: this utilises OZ access controls, and the owner is set in the constructor 1. **Events**: - `Create`, `Withdraw` - Note: No events emmited on the periphery for claim 2. **Error Handling**: - Reverts for invalid token addresses, zero amounts, and end times in the past. ### Specific Checks done - Constructor mismatch - Access control - Overflow/underflow - Rentrancy - Unchecked external call - Loops - Dependency management - Logic review - Functionality ### Test checks - All tests are passing - Coverage report as of now - ![Screenshot 2023-11-23 at 1.53.48 PM](https://hackmd.io/_uploads/BySmhYhVT.png) - `test_MerkleProofs` - check if the merkle proof is generated as is expected - `test_OwnershipOfContract` - check if the owner is set correctly - `test_TransferabilityOfContract` - check the ownership transfer - `test_TransferProcessEmitsTheCorrectEvents` - check the ownership events - `test_CreateFunctionDeploysWithPrecomputeAddress` - check if the precomputed address in create2 is correct - `test_CreateFunctionDeploysWithCorrectAddress` - `test_CreateFunctionDeploysWithCorrectId` - check if the id is length of the distributors - `test_CreateFunctionDeploysTheContractWithCorrectOwnership` - check the ownership of the distributor - `test_CreateFunctionDeploysTheContractWithCorrectToken` - check the token in the distributor - `test_CreateFunctionDeploysTheContractWithCorrectMerkleRoot` - check the merkle root - `test_CreateFunctionDeploysTheContractWithCorrectEndTime` - check the deadline - `test_CreateFunctionChangesTheTokenBalancesCorrectly` - check if the distributor has the right balance post creation - `test_CreateFunctionDoesNotLeaveAnyTokensInPeriphery` - check periphery does not hold any tokens post create - `test_CreateFunctionCallsTheTransferFunctionCorrectly` - check the transfer call made in the periphery - `test_CreateFunctionEmitsTheCreateEvent` - check if create event is emmited when the create function is called - `test_CreateFunctionRevertsWhenTokenAddressIsZero` - check if error is emmited it reverts when token address is zero - `test_CreateFunctionRevertsWhenTotalAmountIsZero` - check if error is emitted when amount is zero - `test_CreateFunctionRevertsWhenEndTimeIsInThePast` - check if error is emitted when deadline is in the past - `test_CreateFunctionRevertsWhenCalledByNonOwner` - check if error is emmitted when called by non owner - `test_AreClaimedFunctionCallsTheIsClaimedFunctionFromDistributors` - check claims are working as expected - `test_ClaimFunctionCallsTheClaimFunctionFromDistributors` - check if the claim is called on the correct distributor peripheries - `test_WithdrawFunctionCallsWithdrawFunctionFromDistributors` - check if the withdraw id working as expected - `test_WithdrawFunctionReturnsTheAmountWithdrawn` - checks if the amount withdrawn is the same as the input - `test_WithdrawFunctionDoesNotLeaveTokensInPeriphery` - check if withdraw doesnt leave any funds in the periphery - `test_WithdrawFunctionChangesTokenBalancesCorrectly` - check if withdraw function updates balance correctly - `test_WithdrawFunctionCallsTheTransferFunctionsCorrectly` - check if the withdraw function calls the correct transfer call - `test_WithdrawFunctionEmitsTheWithdrawEventsCorrectly` - check the withdraw event emitted - `test_WithdrawFunctionRevertsWhenCalledByNonOwner` - checks the error emitted when withdraw is called by a non user - Things that need better tests - Merkle proof generation is tested with only 1 example - All the array inputs are fuzzed mostly with length < 100, the test fails to execute when you put larger constraints e.g. 1000 - Though unit tests are exhaustive there are no invariants tested and there is no integration testing - test_CreateFunctionDeploysWithCorrectAddress - Inconclusive test