Hardcoded Deployment Parameters Are Mutable Storage Instead of Constants
Description
In script/Deploy.s.sol, the deployment configuration is hardcoded as state variables:
address public s_zkSyncUSDC = 0x1D17CbCf0D6d143135be902365d2e5E2a16538d4;
bytes32 public s_merkleRoot = 0xf69aaa25bd4dd10deb2ccd8235266f7cc815f6e9d539e9f4d47cae16e0c36a05;
uint256 public s_amountToAirdrop = 4 * (25 * 1e6);
These values are never updated and are only used as fixed deployment parameters.
Keeping immutable configuration as mutable storage is unnecessary and reduces clarity. It also weakens intent signaling: readers cannot immediately distinguish truly fixed constants from values that may be changed in future script flows.
Additionally, run() uses a second hardcoded USDC literal in transfer(...) instead of reusing s_zkSyncUSDC, which introduces avoidable duplication.
Risk
Likelihood: High
This is definitely present in the codebase and always affects maintainability/readability of the deployment script.
Impact: Low
The issue does not directly create an exploitable on-chain vulnerability in the deployed MerkleAirdrop contract. The impact is limited to code quality, maintainability, and configuration hygiene.
Proof of Concept
The variable declarations in Deploy.s.sol are storage-backed and non-constant:
rg -n "s_zkSyncUSDC|s_merkleRoot|s_amountToAirdrop" script/Deploy.s.sol
Output:
8: address public s_zkSyncUSDC = 0x1D17CbCf0D6d143135be902365d2e5E2a16538d4;
9: bytes32 public s_merkleRoot = 0xf69aaa25bd4dd10deb2ccd8235266f7cc815f6e9d539e9f4d47cae16e0c36a05;
11: uint256 public s_amountToAirdrop = 4 * (25 * 1e6);
16: MerkleAirdrop airdrop = deployMerkleDropper(s_merkleRoot, IERC20(s_zkSyncUSDC));
18: IERC20(0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4).transfer(address(airdrop), s_amountToAirdrop);
Thus, fixed deployment values are currently stored as mutable state variables, and the token address is duplicated in two different literals/usages.
Recommended Mitigation
Declare fixed deployment parameters as constant and reuse them consistently.
contract Deploy is Script {
- address public s_zkSyncUSDC = 0x1D17CbCf0D6d143135be902365d2e5E2a16538d4;
- bytes32 public s_merkleRoot = 0xf69aaa25bd4dd10deb2ccd8235266f7cc815f6e9d539e9f4d47cae16e0c36a05;
+ address public constant ZKSYNC_USDC = 0x1D17CbCf0D6d143135be902365d2e5E2a16538d4;
+ bytes32 public constant MERKLE_ROOT = 0xf69aaa25bd4dd10deb2ccd8235266f7cc815f6e9d539e9f4d47cae16e0c36a05;
// 4 users, 25 USDC each
- uint256 public s_amountToAirdrop = 4 * (25 * 1e6);
+ uint256 public constant AMOUNT_TO_AIRDROP = 4 * (25 * 1e6);
function run() public {
vm.startBroadcast();
- MerkleAirdrop airdrop = deployMerkleDropper(s_merkleRoot, IERC20(s_zkSyncUSDC));
+ MerkleAirdrop airdrop = deployMerkleDropper(MERKLE_ROOT, IERC20(ZKSYNC_USDC));
// Send USDC -> Merkle Air Dropper
- IERC20(0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4).transfer(address(airdrop), s_amountToAirdrop);
+ IERC20(ZKSYNC_USDC).transfer(address(airdrop), AMOUNT_TO_AIRDROP);
vm.stopBroadcast();
}
}
This improves readability, removes duplicate literals, and makes deployment intent explicit.