Beginner FriendlyDeFiFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

Improper amount value used for the Merkle Tree Root calculation at `s_merkleRoot` in the `Deploy.s.sol` script, leads to a Denial of Service (DoS) attack

Description The makeMerkle.js script generates the Merkle Tree root (s_merkleRoot) used within the Deploy.s.sol script. However, there is a discrepancy in the decimal precision settings: while the script utilizes 1e18 as a base value, the USDC token, which interacts with the contract, has only 6 decimals (i.e., 1e6). Consequently, the calculation of the s_merkleRoot value in the deployment script is incorrect due to this precision mismatch. This inconsistency poses a critical issue as it prevents the contract from accurately verifying Merkle proofs provided by users. Without proper verification, the contract is vulnerable to denial-of-service (DoS) attacks, as none of the chosen users would be able to claim their tokens.

address public s_zkSyncUSDC = 0x1D17CbCf0D6d143135be902365d2e5E2a16538d4;
@> bytes32 public s_merkleRoot = 0xf69aaa25bd4dd10deb2ccd8235266f7cc815f6e9d539e9f4d47cae16e0c36a05; // Merkle root initialized for 25e18 value
@> uint256 public s_amountToAirdrop = 4 * (25 * 1e6); // Amount to drop is in 4 batches of 25e6
@> const amount = (25 * 1e18).toString() // as shown above, there is a mismatch in the amount decimals
const userToGetProofOf = "0x20F41376c713072937eb02Be70ee1eD0D639966C"
const values = [
[userToGetProofOf, amount],
["0x277D26a45Add5775F21256159F089769892CEa5B", amount],
["0x0c8Ca207e27a1a8224D1b602bf856479b03319e7", amount],
["0xf6dBa02C01AF48Cf926579F77C9f874Ca640D91D", amount]
]
@> const tree = StandardMerkleTree.of(values, ["address", "uint256"]) // Merkle Tree is generated with the 1e18 base amount

Impact If the current deploy script is used as is, none of the users would be able to claim their tokens, due to invalid Merkle Tree proof calculations. This in turn will render the protocol useless.

Proof of Concept The DoS attack is shown in the MerkleAirdropTest::test_deploy_script_makes_protocol_unusable test. For the sake of the test, the bytes32 public merkleRoot value in the test MerkleAirdropTest.t.sol was changed to the one generated (i.e. 0xf69aaa25bd4dd10deb2ccd8235266f7cc815f6e9d539e9f4d47cae16e0c36a05) from the JS script. Also, the bytes32 proofOne and bytes32 proofTwo values were changed to correspond to the JS script generated one (i.e. 0x4fd31fee0e75780cd67704fbc43caee70fddcaa43631e2e1bc9fb233fada2394 and 0xc88d18957ad6849229355580c1bde5de3ae3b78024db2e6c2a9ad674f7b59f84).

POC
function test_deploy_script_makes_protocol_unusable() public {
uint256 fee = airdrop.getFee();
uint256 merkleTreeAmount = 25e18;
uint256 deployScriptAmount = 25e6;
vm.deal(collectorOne, fee * 2);
// Using the amount intended to collect 25e6 USDC
// We get a revert due to the invalid proof
vm.startPrank(collectorOne);
vm.expectRevert(MerkleAirdrop.MerkleAirdrop__InvalidProof.selector);
airdrop.claim{ value: fee }(collectorOne, deployScriptAmount, proof);
vm.stopPrank();
// Using the merkle tree amount 25e18 USDC
// We still get a revert, however the error is due to insufficient balance as the contract was only loaded with 100e6
vm.startPrank(collectorOne);
vm.expectRevert(
abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, address(airdrop), 100e6, 25e18)
);
airdrop.claim{ value: fee }(collectorOne, merkleTreeAmount, proof);
vm.stopPrank();
}

Tools used Manual review + Foundry

Recommended Mitigation One feasible solution involves regenerating a new Merkle Tree Root with the correct amount and substituting that value in the Deploy.s.sol script before deploying the protocol. This ensures that the correct amount is used in the Merkle proofs, aligning with the token's decimal precision and preventing potential verification failures. Alternatively, implementing a mechanism for the owner to dynamically set a new Merkle Root value offers flexibility in handling scenarios involving invalid amounts or addresses. This approach allows for the distribution of tokens even in the presence of discrepancies. Additionally, it enables the utilization of the currently unused MerkleRootUpdated event. However, it's essential to exercise caution with this solution. Granting the owner the ability to set the Merkle Root dynamically opens up the possibility of malicious behavior. The owner could potentially exploit this capability to drain the contract of tokens by setting arbitrary Merkle Root values. Therefore, strict access controls and thorough auditing mechanisms should be implemented to mitigate the risk of misuse.

- bytes32 private immutable i_merkleRoot;
+ bytes32 private merkleRoot;
+ function updateMerkleRoot(bytes32 calldata _merkleRoot) external onlyOwner {
+ merkleRoot = _merkleRoot;
+ emit MerkleRootUpdated(_merkleRoot);
+ }
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

wrong-usdc-decimals-in-merkle

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.