Animecoin engaged Guardian to review the security of their cross-chain token claimer. From the 29th of December to the 9th of January, a team of 3 auditors reviewed the source code in scope.
- Published
- Review window
- December 29, 2024 to January 9, 2025
- Language
- Solidity
- Chains
- Arbitrum
- Sector
- Token launches
- 0 Critical
- 1 High
- 3 Medium
- 8 Low
- 0 Informational
Scope
Overview
Animecoin engaged Guardian to review the security of their cross-chain token claimer. From the 29th of December to the 9th of January, a team of 3 auditors reviewed the source code in scope.
Issues Detected Throughout the engagement 1 High/Critical issues were uncovered and promptly remediated by the Animecoin team. Several issues impacted the fundamental behavior of the protocol, following their remediation Guardian believes the protocol to uphold the functionality described for the claimer.
Findings 12
-
H-01 High OApp Frozen Due To Ownership Revert Resolved
Description
In the
_checkNFTClaimfunction the owner of thetokenIdis checked with theownerOffunction, however this function will often revert for many NFT collections and thus cause a DoS for the OAPP preventing all subsequent reads.This occurs because the DVNs cannot process the read request, producing an error of
UnresolvableCommandwhich prevents the DVN from verifying their response for the nonce.When DVNs have not verified the response for a nonce, validation in the
EndpointV2contract prevents any subsequent messages for the OApp from being processed inlzReceive:https://github.com/LayerZero-Labs/LayerZero-v2/blob/7da76840e41dc593d3c2007ce35b911b1d8
16b4b/packages/layerzero-v2/evm/protocol/contracts/MessagingChannel.sol#L138
This produces a block of read messages on the chain which the invalid read request was triggered from. The delegate of the OApp can call the skip function on the endpoint in order to skip the affected nonce which cannot be verified and resume
lzReceiveprocessing.However a malicious actor can continue to trigger read requests which will revert and place the OApp in a locked state.
Recommendation
Wrap the call to
ownerOfin a try/catch and return false if theownerOfcall reverts as a validation response to indicate that the claim is not valid.Resolution
Animecoin Team: Resolved.
-
M-01 Medium Vested Amounts Stolen After Ownership Transfer Partially resolved
Description
Because
lzReadreceptions can revert initially and be retried at a later time it is possible for malicious actors to steal vested amounts after an NFT has already been transferred to a new user.For example, consider the following chain of events:
- Alice has Azuki #7 with 10 ANIME tokens vested on day 10
- The day 10 withdrawal limit has already been met on Arbitrum
- Alice intentionally submits a claim request that will fail upon
lzReceivedue to the withdrawal limit,
but validates Alice as the owner of Azuki #7 up to block.timestamp of day 10, allowing Alice to claim 10 ANIME tokens
- On day 11 Alice lists their Azuki #7 for sale under the pretense of the buyer being able to claim the
10 vested ANIME tokens
- Azuki #7 is sold to Bob on day 11
- Alice retries their
lzReceiveon Arbitrum by invoking thelzReceivefunction through the endpoint
themselves. The read result is still in the message channel so this action is allowed.
- Alice claims the 10 vested ANIME tokens that had accrued up to day 10
- Bob purchased Azuki #7 under the pretense that he would also receive these 10 vested ANIME
tokens, however after his purchase Alice took those 10 ANIME tokens from him
Recommendation
Retries of
lzReadresults can be disabled by simply returning without claiming tokens in thelzReceivefunction. This would require users to re-submit arequestClaimtransaction when their read request fails due to the withdrawal limit, contract pause, etc. However L2 transaction costs +lzReadcosts are cheap so this may be acceptable.Additionally, consider including validation that the withdrawal limit is not met for the current day in the
requestClaimfunction to disallow users from potentially wasting gas + the lzRead fee when this scenario arises. This also allows for a quicker feedback loop on the withdrawal limit error.Resolution
Animecoin Team: Partially Resolved.
-
M-02 Medium setReadChannel Always Assigns lzReadChannel Logical Error Resolved
Description
In the
setReadChannelfunction theactiveparameter determines whether the channel is being activated or deactivated. However the$.lzReadChannel = channelId;assignment is always made no matter theactivevalue.Therefore when a read channel is deactivated it is assigned as the active
lzReadChannel. This will put the app in an unintentional DoS’d state since the peer is no longer assigned for this channel.Recommendation
Do not assign the
lzReadChannelvalue ifactiveis false.Resolution
Animecoin Team: Resolved.
-
M-03 Medium ERC721 Delegations With Rights Are Missed Logical Error Resolved
Description
In the
_checkNFTClaimfunction the following boolean condition is used to determine if the claimer is authorized to initiate the ANIME claim for thenftOwner.DelegateCheckerLib.checkDelegateForERC721(claimer, nftOwner, nftContractAddress, tokenId) ||
_checkDelegateV2Rights(claimer, nftOwner)However the
_checkDelegateV2Rightsfunction does not check for ERC721 specific delegations, it checks for global delegations for thenftOwneraddress.Therefore if the
nftOwnerissues an ERC721 token delegation to the claimer address with the specificSUBDELEGATION_RIGHTS_KEYthe delegation will not be seen as a valid authorization of the claimer.Recommendation
Create a new
_checkDelegateV2RightsForNftfunction which usescheckDelegateForERC721.Resolution
Animecoin Team: Resolved.
-
L-01 Low Dangerous NFT To UUID Update Acknowledged
Description
The
setNFTToUUIDfunction allows the owner to update thenftToUUIDvalue thus changing the UUID which an NFT corresponds to.This implies that it may be possible that a signature is provided which shows that UUID X is associated with NFT A at one point in time and then subsequently after correction a signature is provided that shows that UUID X is associated with NFT B at another time.
If this is the case, then a malicious actor may retrieve the original signature which shows that UUID X is associated with NFT A and use this signature for their own NFT A to claim the vest of NFT B.
Also be aware that updating the NFT to UUID mapping allows for more than the original vesting allocation to be claimed, since the vesting state storage slot which stores the
withdrawnAmountis unique to the nft andtokenId, not the UUID.Recommendation
Be aware of these risks when making any updates with the signatures and
nftToUUIDmapping. To avoid the risk of stale signature replay in these scenarios consider assigning a new UUID signer after every update so that previous signatures cannot be used.Otherwise every affected NFT can be force assigned the most recent correct UUID in the
nftToUUIDmapping. To resolve thewithdrawnAmountdiscrepancy, consider including an admin function to update thewithdrawnAmountof a user’s claim.Otherwise be sure to update the merkle proof accordingly to reduce the user’s allocation by the already withdrawn amount.
Resolution
Animecoin Team: Acknowledged.
-
L-02 Low Unused DelegateCheckerLib Resolved
Description
In the
ClaimCheckercontract the_DELEGATE_REGISTRY_V2is called directly in the_checkDelegateV2Rightsfunction without using theDelegateCheckerLib.However the
DelegateCheckerLibperforms a more efficient call, supporting the bytes32 rights parameter with the overloadedcheckDelegateForAllfunction.Recommendation
Consider using the
DelegateCheckerLibfor theSUBDELEGATION_RIGHTS_KEYcheck as well.Resolution
Animecoin Team: Resolved.
-
L-03 Low Claim Requests Allowed When At Daily Limit Acknowledged
Description
In the
requestClaimfunction there is no validation that prevents users from requesting claims that will trivially fail when the daily withdrawal limit has been reached.Recommendation
Consider adding validation which prevents users from requesting claims when the daily withdrawal limit has already been reached to save users from potentially wasting gas and LayerZero fees.
Resolution
Animecoin Team: Acknowledged.
-
L-04 Low Duplicate DVN Configured Resolved
Description
The
LayerZeroDVN is unnecessarily repeated as a required DVN and an optional DVN, however for the desired behavior of 2/3 DVNs with one of them always beingLayerZerothen theLayerZeroDVN should be included only once in therequiredDvnslist with the optional DVNs list being the other DVNs with anoptionalDVNThresholdof 1.Furthermore, currently there are 4 total unique DVNs included in the config file, meaning that one of the non-LayerZero DVNs should be removed from the optional DVNs list to make it a 2/3 total verification.
Recommendation
Remove the
LayerZeroDVN from theoptionalDVNslist as well as another of the DVNs from theoptionalDVNslist. Then reduce theoptionalDVNThresholdto 1 to achieve a 2/3 total DVNs verification where one DVN must always beLayerZero.Resolution
Animecoin Team: Resolved.
-
L-05 Low Lacking Maximum Configs Validation Resolved
Description
In the
AnimeClaimercontract therequestClaimfunction does not include any validation on the maximum length of theconfiglist.This may be a good validation to include to avoid any unexpected behaviors while providing DVNs with requests that include an unnecessarily large amount of
tokenIdsto claim for.It’s not clear what the upper limit is for return data that the executor and/or DVNs can process and given the implications of a potentially stuck OApp like in C-01 it may be best to avoid this undefined behavior altogether by limiting the amount of configs provided to an individual
requestClaimcall to a reasonable amount.Recommendation
Consider validating that the configs.length is within a reasonable limit.
Resolution
Animecoin Team: Resolved.
-
L-06 Low Dangerous Reentrancy Guard Acknowledged
Description
The
checkClaimsfunction in theClaimCheckercontract uses anonReentrantmodifier to prevent any potentially malicious contracts from re-entering into thecheckClaimsfunction during the DVN simulation.However if a malicious contract somehow does reenter into the
checkClaimsfunction and trigger thenonReentrantmodifier to revert this will cause the OApp to enter a stuck state as described in C-01.Recommendation
No pathway for this to occur has been identified, however out of an abundance of caution instead of using a
nonReentrantmodifier, consider using anENTEREDstorage variable and setting it as true at the beginning of thecheckClaimsfunction.Instead of reverting if a call reenters the
checkClaimsfunction, simply return false to indicate that the claim could not be verified. This allows a malicious actor to DoS a single claim, but they cannot freeze the OApp and DoS all subsequent read messages if this behavior is implemented.Resolution
Animecoin Team: Acknowledged.
-
L-07 Low Rights Key Does Not Follow Convention Resolved
Description
The
SUBDELEGATION_RIGHTS_KEYin theClaimCheckercontract is declared as the bytes of a string, however typically rights keys are hashed strings with keccak256. An example of this is the upcoming Shadow rights for APE chain: https://x.com/0xQuit/status/1869499764464906373Recommendation
Consider using a hash of the
"AN_CLAIMER_PERMS"string instead of the bytes of the string directly.Resolution
Animecoin Team: Resolved.
-
L-08 Low Contracts Are Whitelisted For All Claims Acknowledged
Description
The
explicitContractClaimersmapping is used both for NFT claims and collector claims. Therefore if a user explicitly designates a contract claimer then that claimer is able to claim all NFT claims and collector level claims for the user.This may be unexpected for the user as they may expect to be able to designate a contract claimer as the claimer for an individual NFT or just NFT claims in general.
Recommendation
Be aware of this behavior, if intended then be sure to document it clearly to users.
Resolution
Animecoin Team: Acknowledged.
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.
