Skip to content

Commit 515221c

Browse files
authored
audit: claim contracts (#2288)
1 parent 8fec550 commit 515221c

11 files changed

Lines changed: 699 additions & 11 deletions

File tree

814 KB
Binary file not shown.
228 KB
Binary file not shown.

claim_contracts/foundry.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ libs = ["lib"]
55
optimizer = true
66
optimizer_runs = 999_999
77

8-
solc_version = "0.8.28"
8+
solc_version = "0.8.35"
99

1010
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
1111
fs_permissions = [
1212
{ access = "read-write", path = "script-out/" },
1313
{ access = "read", path = "script-config/" },
14+
{ access = "read", path = "test/fixtures/" },
1415
]

claim_contracts/src/AlignedToken.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: MIT
2-
pragma solidity 0.8.28;
2+
pragma solidity 0.8.35;
33

44
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
55
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";

claim_contracts/src/ClaimableAirdrop.sol

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// SPDX-License-Identifier: MIT
2-
pragma solidity 0.8.28;
2+
pragma solidity 0.8.35;
33

44
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
56
import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
67
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
78
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
@@ -11,13 +12,24 @@ import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/acces
1112
/// @title Claimable Airdrop
1213
/// @notice This contract is the implementation of the Claimable Airdrop
1314
/// @dev This contract is upgradeable and should be used only through the proxy contract
15+
/// @dev Operational edge cases that cannot be enforced on-chain (the contract only
16+
/// stores the Merkle root, never the full set of leaves):
17+
/// - Revoking unclaimed approvals: the owner can call `updateMerkleRoot` (while
18+
/// paused) to replace the root with one that no longer contains previously valid
19+
/// but unclaimed approvals, effectively revoking them.
20+
/// - First-come, first-served distribution: the contract cannot verify that
21+
/// `tokenDistributor` holds (and has approved) enough tokens to cover every
22+
/// approval. If the distributor is underfunded, later claims revert until it is
23+
/// topped up, so claimants should claim as early as possible.
1424
/// @custom:security-contact security@alignedfoundation.org
1525
contract ClaimableAirdrop is
1626
Initializable,
1727
ReentrancyGuardUpgradeable,
1828
PausableUpgradeable,
1929
Ownable2StepUpgradeable
2030
{
31+
using SafeERC20 for IERC20;
32+
2133
/// @notice Address of the token contract to claim.
2234
address public tokenProxy;
2335

@@ -94,12 +106,7 @@ contract ClaimableAirdrop is
94106

95107
_verifyAndMark(amount, validFrom, merkleProof);
96108

97-
bool success = IERC20(tokenProxy).transferFrom(
98-
tokenDistributor,
99-
msg.sender,
100-
amount
101-
);
102-
require(success, "Failed to transfer funds");
109+
IERC20(tokenProxy).safeTransferFrom(tokenDistributor, msg.sender, amount);
103110

104111
emit TokensClaimed(msg.sender, amount);
105112
}
@@ -132,12 +139,11 @@ contract ClaimableAirdrop is
132139

133140
require(totalClaimable > 0, "Nothing to claim");
134141

135-
bool success = IERC20(tokenProxy).transferFrom(
142+
IERC20(tokenProxy).safeTransferFrom(
136143
tokenDistributor,
137144
msg.sender,
138145
totalClaimable
139146
);
140-
require(success, "Failed to transfer funds");
141147

142148
emit TokensClaimed(msg.sender, totalClaimable);
143149
}
@@ -206,4 +212,11 @@ contract ClaimableAirdrop is
206212
function unpause() external onlyOwner {
207213
_unpause();
208214
}
215+
216+
/// @notice Prevents the owner from renouncing ownership.
217+
/// @dev Renouncing would set the owner to address(0), permanently disabling
218+
/// `updateMerkleRoot`, `extendClaimPeriod`, `pause`, and `unpause`.
219+
function renounceOwnership() public view override onlyOwner {
220+
revert("Cannot renounce ownership");
221+
}
209222
}

0 commit comments

Comments
 (0)