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

Wrong USDC address provided in `Deploy.s.sol` script caused broken `MerkleAirdrop` would be deployed instead

Summary

Wrong USDC address used in Deploy.s.sol script causing the MerkleAirdrop contract cannot be used as intended

Vulnerability Details

Deploy.s.sol script using 0x1D17CbCf0D6d143135be902365d2e5E2a16538d4 instead of 0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4 would break the contract deployed because the former is not an ERC20 address instead it is a normal address. So it does not have safeTransfer function called on MerkleAirdrop::claim function.

Deploy.s.sol:

contract Deploy is Script {
@> address public s_zkSyncUSDC = 0x1D17CbCf0D6d143135be902365d2e5E2a16538d4;
bytes32 public s_merkleRoot = 0xf69aaa25bd4dd10deb2ccd8235266f7cc815f6e9d539e9f4d47cae16e0c36a05;
// 4 users, 25 USDC each
uint256 public s_amountToAirdrop = 4 * (25 * 1e6);
PoC

Add your zksync rpc url ZKSYNC_MAINNET_RPC_URL to .env file.

Add this helper code to Deploy.s.sol so our test can capture the address of MerkleAirdrop and IERC20 contract:

- function run() public {
+ function run() public returns (MerkleAirdrop, IERC20) {
vm.startBroadcast();
MerkleAirdrop airdrop = deployMerkleDropper(s_merkleRoot, IERC20(s_zkSyncUSDC));
// Send USDC -> Merkle Air Dropper
IERC20(0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4).transfer(address(airdrop), s_amountToAirdrop);
vm.stopBroadcast();
+ // add helper
+ return (airdrop, IERC20(s_zkSyncUSDC));
}

Add MerkleAirdropDeployScriptTest.t.sol to test folder.

MerkleAirdropDeployScriptTest.t.sol:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import { MerkleAirdrop, IERC20 } from "../src/MerkleAirdrop.sol";
import { AirdropToken } from "./mocks/AirdropToken.sol";
import { Deploy } from "script/Deploy.s.sol";
import { Test } from "forge-std/Test.sol";
import { console } from "forge-std/Test.sol";
contract MerkleAirdropTest is Test, Deploy {
MerkleAirdrop public airdrop;
IERC20 public tokenIERC20;
AirdropToken public token;
uint256 amountToCollect = (25 * 1e6); // 25.000000
uint256 amountToSend = amountToCollect * 4;
address collectorOne = 0x20F41376c713072937eb02Be70ee1eD0D639966C;
// using proof from makeMerkle.js
bytes32 proofOne = 0x4fd31fee0e75780cd67704fbc43caee70fddcaa43631e2e1bc9fb233fada2394;
bytes32 proofTwo = 0xc88d18957ad6849229355580c1bde5de3ae3b78024db2e6c2a9ad674f7b59f84;
bytes32[] public proof = [proofOne, proofTwo];
function setUp() public {
// assume deployer have enough USDC balance, we use deal
address correctUSDCAddress = 0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4;
deal(address(IERC20(correctUSDCAddress)), address(msg.sender), amountToSend);
console.log("USDC before:", IERC20(correctUSDCAddress).balanceOf(address(msg.sender)));
// running the Deploy.s.sol script
(airdrop, tokenIERC20) = Deploy.run();
console.log("USDC after:", IERC20(correctUSDCAddress).balanceOf(address(msg.sender)));
}
function testUsingDeployScriptContractBrokenBecauseWrongUSDCAddress() public {
uint256 startingBalance = tokenIERC20.balanceOf(collectorOne);
vm.deal(collectorOne, airdrop.getFee());
vm.startPrank(collectorOne);
airdrop.claim{ value: airdrop.getFee() }(collectorOne, amountToCollect, proof);
vm.stopPrank();
uint256 endingBalance = tokenIERC20.balanceOf(collectorOne);
assertEq(endingBalance - startingBalance, amountToCollect);
}
}

after that run the following command forge test --zksync --fork-url $ZKSYNC_MAINNET_RPC_URL --mt testUsingDeployScriptContractBrokenBecauseWrongUSDCAddress

the result should REVERT:

Failing tests:
Encountered 1 failing test in test/MerkleAirdropDeployScriptTest.t.sol:MerkleAirdropTest
[FAIL. Reason: EvmError: Revert] testUsingDeployScriptContractBrokenBecauseWrongUSDCAddress() (gas: 7344)

Impact

Contract MerkleAirdrop broken and cannot be used.

Tools Used

manual review and foundry

Recommendations

Change the Deploy.s.sol value of s_zkSyncUSDC to correct value 0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4.

Deploy.s.sol:

contract Deploy is Script {
- address public s_zkSyncUSDC = 0x1D17CbCf0D6d143135be902365d2e5E2a16538d4;
+ address public s_zkSyncUSDC = 0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4;
bytes32 public s_merkleRoot = 0xf69aaa25bd4dd10deb2ccd8235266f7cc815f6e9d539e9f4d47cae16e0c36a05;
// 4 users, 25 USDC each
uint256 public s_amountToAirdrop = 4 * (25 * 1e6);

furthermore on MerkleAirdrop contract, we can add check whether the variable passed on constructer is smartcontract or not. This is not a silver bullet solution as there are problems like passing wrong ERC20 address too. But for this specific problem in this audit, this can help mitigating passing EOA address instead of contract address.

MerkleAirdrop.sol:

constructor(bytes32 merkleRoot, IERC20 airdropToken) Ownable(msg.sender) {
+ uint256 size;
+ assembly {
+ size := extcodesize(airdropToken)
+ }
+ require(size > 0, "airdropToken address not a contract");
i_merkleRoot = merkleRoot;
i_airdropToken = airdropToken;
}
Updates

Lead Judging Commences

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

usdc-wrong-address

Support

FAQs

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