Platform: Code4rena
Start Date: 04/11/2021
Pot Size: $50,000 USDC
Total HM: 20
Participants: 28
Period: 7 days
Judge: 0xean
Total Solo HM: 11
Id: 51
League: ETH
Rank: 17/28
Findings: 2
Award: $615.76
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: gpersoon
Also found by: elprofesor, fr0zn, pauliax
elprofesor
Due to improper validation of input, approved airdrop users are able to double spend airdrop allocated tokens. This is due to insufficient validation in validate()
and claimExact()
which allows the user to reset the amount of tokens they have claimed.
Attacker Executes the following sequence of transactions:
airdrop[msg.sender].amount
to the amount hardcoded in the airdropBalancesclaim()
has the following code assert(airdrop[msg.sender].amount - claimable != 0);
and validate()
has the following require(airdrop[msg.sender].amount == 0, "Already validated.");
Unfortunately, the claimExact()
function does not have the same assertion allowing the amount for a user to be lowered to zero.Brownie test
def test_double_claim(airdrop, chain, token, vesting): chain.sleep(ONE_WEEK//2) airdrop.validate({"from":AIRDROP_ADDR}) init_supply = 20 * 10 ** 18 assert(token.balanceOf(AIRDROP_ADDR) == 0) fraction = 10**18 * (AIRDROP_EXPECTED * 10 ** 18) / init_supply airdrop_user = airdrop.airdrop(AIRDROP_ADDR) assert airdrop_user["fraction"] - fraction == 0 avail_supp = airdrop.available_supply() exact_amount = avail_supp * (airdrop_user["fraction"]) / 10 ** 18 assert exact_amount == avail_supp * (airdrop_user["fraction"]) / 10 ** 18 airdrop.claimExact(exact_amount - 100, {"from": AIRDROP_ADDR}) balance = token.balanceOf(AIRDROP_ADDR) assert balance == (3 * avail_supp * airdrop_user["fraction"]) / (10**19) airdrop_user = airdrop.airdrop(AIRDROP_ADDR) assert airdrop_user["amount"] == 0 airdrop.validate({"from": AIRDROP_ADDR}) airdrop.claimExact(exact_amount - 100, {"from": AIRDROP_ADDR}) balance = token.balanceOf(AIRDROP_ADDR) assert balance == 2* (3 * avail_supp * airdrop_user["fraction"]) / (10**19) # This assert shows that the user has managed to withdraw more funds than initially allocated airdrop_user = airdrop.airdrop(AIRDROP_ADDR) assert airdrop_user["amount"] == 0
NOTE: For test to work supply was hard coded to uint256 private airdrop_supply = 20 * 10 ** 18;
and _available_supply()
was changed to use a fixed value. This does not impact the test functionality, but just aids in making the POC easier create without having to manage a large number of EPOCH intervals
function _available_supply() private view returns(uint256) { // assert(block.timestamp - startEpochTime <= RATE_TIME); // return startEpochSupply + (block.timestamp - startEpochTime) * rate; return airdrop_supply; }
Manual Review
Several mitigation strategies exist
claimExact
if this is the intended functionality expected (ie... user shouldn't have an amount of zero)validate()
that the require(airdrop[msg.sender].claimed != airdrop[msg.sender].maxClaimable, "Already claimed.");
#0 - chickenpie347
2022-01-03T23:47:47Z
Duplicate of #101
elprofesor
Swap.sol inherits ownerpausable which inherits from Open Zep Ownable. This ownable contract allows for the transfer of ownership without validating that own address is a valid address in control of some expected recipient. If this function is used incorrectly or by accident, the admin user may be lost or set to a malicious account.
Implement a transfer-accept ownership pattern in Swap.sol contract. This allows an owner to accept the transfer insuring that the account is controlled by a valid user.
#0 - chickenpie347
2021-11-16T13:56:21Z
Addressed in #90.
#1 - CloudEllie
2022-01-05T02:18:18Z
Looks like #35 is the primary