Guardian's review of ANIME Claimer for Animecoin, published December 2024. The report records 23 findings across 2 review rounds, including 2 critical and 1 high.
- Published
- Review window
- December 4 to 17, 2024
- Rounds
- Main Review, Remediation Review
- Language
- Solidity
- Chains
- Ethereum, Arbitrum
- Sector
- Tokens
- 2 Critical
- 1 High
- 4 Medium
- 16 Low
- 0 Informational
Scope
2 files in scope · 382 nSLOC
| File | nSLOC | Lines |
|---|---|---|
contracts/Timelock.sol | 10 | 13 |
contracts/AnimeClaimer.sol | 372 | 676 |
Findings 23
Main Review
18 findings · December 4 to 6, 2024-
C-01 Critical Unable To Unpause Contract Logical Error Resolved
Description
When the AnimeClaimer is initially created, the storage variable
pausedis set to true within the constructor:_getAnimeClaimerStorage().paused = true;For the AnimeClaimer to become unpaused, it is expected the owner properly configures the contract's other parameters such as
dailyTotalWithdrawnLimitand then calls functionsetPaused(false). The issue is that when attempting to unpause the contract, the code also checksif (!readyForClaim()).Function
readyForClaimchecks whether the contract is paused, and if so, returns false:if ($.paused) return false;Since the initial state is paused,if (!readyForClaim())will be entered. Therefore when the owner or manager attempts to unpause the contract, it reverts withrevert NotReadyForClaim();Ultimately the entire contract's functionality is DoS'd as it is not possible to unpause the contract.Recommendation
When unpausing, do not revert if the contract is currently paused. Contract configuration parameters can be verified to be set outside of
readyForClaimto avoidif ($.paused) return false; -
C-02 Critical Permanent Claim DoS Logical Error Resolved
Description
The
_claimBatchfunction prevents withdrawals once the daily withdrawal limit has been met:if ($.dailyTotalWithdrawnLimit <= $.dailyTotalWithdrawn) revert ExceedDailyTotalWithdrawnLimit();The issue is that the
dailyTotalWithdrawnis only reset within function_incrementDailyTotalWithdrawn, but_incrementDailyTotalWithdrawnis only called when a user withdraws a non-zero amount:if (totalWithdrawn != 0)This leads to a total DoS once a user withdraws to the daily limit, as no more withdraws are possible and the daily limit cannot be reset, even if it is a new day.
Recommendation
Update the daily limit regardless of whether a withdraw has occurred. Consider checking if the block timestamp is a new day and reset the
lastWithdrawnDayor simply call_incrementDailyTotalWithdrawn(0)at the beginning ofclaimBatch. -
H-01 High Missing Receive Configuration for Arbitrum Config Resolved
Description
In order for messaging to work, configurations should be handled properly. According to LayerZero documentation:
For a configuration to be considered correct, the Send Library configurations on Chain A must match Chain B's Receive Library configurations for filtering messages.
However as we can see from the configuration file, while the Send Library for source chain is configured, Receive Library for destination chain is not configured, hence the default values will be used for it in Arbitrum.
While the
confirmationsconfigured for Ethereum is “8”, the defaultconfirmationsin Arbitrum is configured as “15”, and this will lead to “Block Confirmation Mismatch” explained as:Messages will be blocked until either the sending OApp has increased the outbound block confirmations, or the receiving OApp decreases the inbound block confirmation threshold.
in LayerZero documentation.
Also default DVN's for Arbitrum are ‘Layerzero Labs’ and ‘Google Cloud’, hence other DVN's configured for source chain (’Nethermind’, ‘Horizen’) won't be utilized as expected and they will just consume fee in source chain.
Recommendation
Implement a proper configuration for destination chain's Receiver Library to match the Sender’s. While doing so following documentation from LayerZero can help: https://docs.layerzero.network/v2/developers/evm/create-lz-oapp/configuring-pathways Be aware of following mismatch scenarios described and be sure Configurations in both end Matches: https://docs.layerzero.network/v2/developers/evm/protocol-gas-settings/default-config#debugging-configurations
For example, the following
receiveConfigblob can be used for the arbitrum → ethereum connection:{ from: arbitrumContract, to: ethereumContract, config: { receiveConfig: { ulnConfig: { // SendUlnConfig and ReceiveUlnConfig confirmations must match confirmations: BigInt(8), requiredDVNs: [ // Layerzero, Arbitrum '0x2f55c492897526677c5b68fb199ea31e2c126416', // Google, Arbitrum '0xd56e4eab23cb81f43168f9f45211eb027b9ac7cc', // Nethermind, Arbitrum '0xa7b5189bca84cd304d8553977c7c614329750d99', // Horizen, Arbitrum '0x19670df5e16bea2ba9b9e68b48c054c5baea06b8', ], optionalDVNs: [], optionalDVNThreshold: 0, }, }, }, } -
M-01 Medium Sanctions Bypass With Delegate Logical Error Acknowledged
Description
if (_SANCTIONS_LIST.isSanctioned(msg.sender) || _SANCTIONS_LIST.isSanctioned(to))is intended to prevent sanctioned addresses from using the AnimeClaimer. However, this protection can be bypassed simply with a sanctioned NFT owner delegating to a non-sanctioned address. That non-sanctioned address can then claim without issues.Recommendation
Consider adding a sanctions list validation if the
msg.senderis a delegate of the NFT owner.Keep in mind that users can always transfer the NFT to another address, enabling them to claim the allocation.
-
M-02 Medium Withdraw Limit Discrepancy between Chains Logical Error Resolved
Description
The variable
dailyTotalWithdrawnLimitcan vary between chains. And indeed according to the runbook, the L1 limit is 1e24, while the L2 limit is currently 1.5e23. However, this discrepancy poses a problem where a message sent from the source chain (L1) may not be received in the destination chain (L2). This issue arises when the allocation sent is larger than L2'sdailyTotalWithdrawnLimitbut smaller than L1'sdailyTotalWithdrawnLimit.Since it is smaller than the L1 limit, it can be sent from the source chain, but because it exceeds the L2 limit, the message becomes stuck at the endpoint and cannot be received by the
AnimeClaimercontract in L2.Recommendation
Make sure the
dailyTotalWithdrawnLimitvalue for L2 is greater or equal to the value set in L1 contract. -
M-03 Medium Missing Validation In
readyForClaimValidation AcknowledgedDescription
AnimeClaimer.readyForClaimis a helper function that ensures the contract is correctly set up to initiate user claims.However, the function is missing crucial validations:
Ethereum
- OApp correctly wired (peers, endpoint, delegate)
Arbitrum
- OApp correctly wired (peers, endpoint, delegate)
- Existing
ANIMEtoken balance
Recommendation
Consider adding the validation above to ensure both contracts are ready to start the claiming process and avoid failed transactions and messages.
-
M-04 Medium Flash Loans Abused To Steal Vest Gaming Partially resolved
Description
There are several protocols which offer NFT flashloans following EIP 6682, which grant ownership of an NFT for a single transaction from a pool of NFTs.
These protocols will allow arbitrary users to flash loan their NFT and claim the corresponding ANIME tokens from the NFTs vest in the merkle root even though they are not the official owners of the NFT.
Additionally, notice that fixed term NFT loans allow the loaner to claim any outstanding vest for the NFT that has not yet been claimed by the owner.
Recommendation
Be aware of this edge case for flash loan providers of NFTs which will receive an ANIME vest. If this is deemed to be an issue those providers can be warned ahead of time to withdraw their NFT from flash loan service before the claimer becomes active.
-
L-01 Low Direct Boolean Check Logical Error Resolved
Description
if (paused == false)can just be rewritten to!pausedwithin functionsetPausedto be consistent with other boolean operations.Recommendation
Replace it with
!paused. -
L-02 Low Chain Inconsistencies Cause Failed Messages Logical Error Acknowledged
Description
If the contract was paused on Arbitrum but not on ETH, a user could still send a claim request which would increment the total withdrawn on ETH, yet the withdraw would revert on the destination chain with
revert Paused();Consequently, thetoaddress will not receive their tokens and the request will have to be retried at the Endpoint after an unpause. This may be unexpected to users who had a successfulclaimBatchcall.Recommendation
Clearly document this behavior to users.
-
L-03 Low Variables Not Set In Setup Script Warning Resolved
Description
The
managervariable is not set for Arbitrum anduuidSigneris not set for ETH withinAnimeClaimerSetupbut other key parameters are. However this is not a major issue since setters are available within the AnimeClaimer contract.Recommendation
Be aware of this and considering setting those variables within the script. Also consider checking if those parameters should be set within
readyForClaim. -
L-04 Low Zero Check for Signer Warning Resolved
Description
The
setUUIDSignerfunction does not limit the signer address and allows zero as a valid address. If the owner changes the signer address to zero by mistake, it will result in every signature check passing in_claimBatch, and the consequences can be catastrophic even if the mistake is resolved promptly upon discovery.Recommendation
If the provided signer address is zero, the
setUUIDSignerfunction should revert. -
L-05 Low Underflow in Duration Calculation Underflow Acknowledged
Description
According to the runbook, if the intention is to claim instantly, one can set the start and end times to the past, resulting in the whole allocation amount being returned optimistically by the
vestedfunction. However, if the start time is set to be greater than the end time, an underflow will occur in the duration calculation before comparing the start and end times withblock.timestampto return the total allocation, leading to a direct revert and preventing that claim.Recommendation
Ensure the end time greater than the start time in the root creation process.
-
L-06 Low Lacking tokenId Validation Validation Acknowledged
Description
When claiming for a collector level claim the tokenId should not be assigned to a nonzero value.
This will be validated in the merkle proof, however out of an abundance of caution it may be prudent to add explicit validations at the contract level that the
tokenIdis 0 whenisForCollectoris true.Recommendation
Consider adding validation that enforces the
tokenIdto be 0 whenisForCollectoris true. -
L-07 Low Smart Contract Claimers Warning Warning Resolved
Description
The
_requestReleasePayloadfunction assigns themsg.senderas the refund receiver for a native refund.As a result, Smart Contracts which receive a collector allocation, do not have the functionality to delegate, and do not have a receive function cannot claim their vest.
Recommendation
This scenario is unlikely to occur, especially for a contract which is able to claim from the claimer. This finding simply serves as a warning for this case.
-
L-08 Low Unnecessary Signature Validation Logical Error Acknowledged
Description
When claiming allocation for Elementals, the
uuidis defined and a signature verification is needed using theuuidSigner.However, the first time the user claims allocation for an Elemental, besides checking the signature, the
uuidis stored in a mapping:$.nftToUUID[c.nft][c.tokenId] = c.uuid;Therefore, the consecutive claims do not need additional signature verification, as the mapping will ensure its a valid claim:if (c.uuid != $.nftToUUID[c.nft][c.tokenId]) revert InvalidUuid();Recommendation
Consider validating the signature only when the mapping is updated with the new
uuid. -
L-09 Low Vesting Root Can Be Re-Initialized Logical Error Acknowledged
Description
The owner of
AnimeClaimercan set the vesting root for the merkle distribution.However, the function does not prevent the reinitialization of the vesting root. Due to the fact that the snapshot is only taken once, there is no need to leave the option for modifying the value again, as it will lead to accounting issues.
Recommendation
Only allow the vesting root to be set once, or include it as a param in the constructor.
-
L-10 Low Storage Variables Can't Be Read Getters Partially resolved
Description
The
AnimeClaimercontract defines a struct for all storage variables. Although there are some getter functions to read this variables, there are some still missing that will provide a better user experience:lastWithdrawnDaydailyTotalWithdrawn
Recommendation
Adding these getters will allow the frontend to correctly display the current contract stats and limits.
-
L-11 Low Optional DVNs Suggestion Suggestion Resolved
Description
The current DVN configuration uses 4 required DVNs, however if there is an outage in any of the 4 DVNs this can cause a backlog of claim messages to build up and eventually be executed all in a single day, exceeding the daily limit.
This can result in DoS'd claims for other users for a period of time.
Recommendation
Consider moving to a setup of optional DVNs whereby for example 4/5 or 3/4 DVNs are required to verify a message before execution. This way no single DVN can prevent a claim message from being executed.
Remediation Review
5 findings · December 16 to 17, 2024-
L-01 Low Lacking lastWithdrawnDay Getter Acknowledged
Description
The
AnimeClaimercontract does not implement a getter function for thelastWithdrawnDayvalue.Recommendation
Consider whether this variable should be exposed through it's own getter function similarly to other storage variables.
-
L-02 Low Incorrect Comment Acknowledged
Description
In the comment for the LayerZero receive configurations on Arbitrum it is mentioned that the receive library configured is
ReceiveUln302 for Ethereum.However the address provided is correctly the
ReceiveUln302library address on Arbitrum.Recommendation
Correct the comment to
ReceiveUln302 for Arbitrum. -
L-03 Low Incorrect Function Name Acknowledged
Description
The
AnimeClaimerL1Setupperforms the setup required forAnimeClaimerin Ethereum.There is an inconsistency in the function name for
setUUIDSigneras the script usessetUuidSigner, causing a revert when its executed.Recommendation
Consider changing
setUuidSignerfor the correct function name in the contract,setUUIDSigner. -
L-04 Low
readyForClaimMay Confuse Claimers Logical Error AcknowledgedDescription
The
readyForClaimfunction signals when the contract has been properly set up for claiming. However, thepausedstate is not included in this function, or in thefullyReadyForClaimfunction.Users may become confused when reading the returned values of these functions, as even if both return true, the contract is not actually ready for claiming until the owner unpauses it.
Furthermore, the UI will need to combine both the
readyForClaimandisPausedgetters to enable claiming.Recommendation
Consider adding the paused state to the
fullyReadyForClaimfunction, and return false if the contract is paused. -
L-05 Low Withdraw Limit Discrepancy Can Still Occur Acknowledged
Description
Although M-02 was fixed in deployment, the setters in the contract do not validate that the L2 withdrawal limit is larger than the L1 limit.
Recommendation
Consider adding validation in
setDailyTotalWithdrawnLimiton thelimitparameter depending on the chain id.
No findings match.
More from Animecoin
Put your code through the same review.
This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.
