Platform: Code4rena
Start Date: 13/12/2023
Pot Size: $36,500 USDC
Total HM: 18
Participants: 110
Period: 8 days
Judge: 0xTheC0der
Id: 311
League: ETH
Rank: 65/110
Findings: 1
Award: $44.03
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: osmanozdemir1
Also found by: 0xCiphky, 0xDING99YA, 0xlemon, 0xluckhu, AS, Abdessamed, BARW, KupiaSec, MrPotatoMagic, SovaSlava, SpicyMeatball, ast3ros, bart1e, hakymulla, ktg, n1punp, plasmablocks, shaka, twcctop
44.0266 USDC - $44.03
There is leftover ether that will be stuck forever in the ERC20TokenEmitter
. This is a result of executing the buyToken
function and sending ether as msg.value
.
ERC20TokenEmitter::buyToken
function is used to buy erc20 votes tokens. If the owner of the contract has set a payment for the creator from setCreatorRateBps
function, then there will be ether stuck if the direct payment entropyRateBps
is not set to 100%.
This happens because when a user sends value to the contract, a small portion of this value is reserved for the builder, purchaseReferral and deployer. Another portion is transferred to the DAO in exchange for minting the user vote tokens, then we have a direct payment for the creator and finally instead of tranferring the creator's left payment, we mint him vote tokens. The problem is exactly in the last step because we mint erc20 tokens for the creator but the value that this erc20 tokens correspond to is left in the contract with no actual way to withdraw it.
Here is a test showcasing the issue:
Add this test in 2023-12-revolutionprotocol\packages\revolution\test\token-emitter\ERC20TokenEmitter.t.sol
and add the console
at the top like that:
import { Test, console } from "forge-std/Test.sol";
To run this test:
forge test --match-contract testStuckEtherAfterBuyToken -vvv
(make sure you are in the 2023-12-revolutionprotocol/packages/revolution
directory)
function testStuckEtherAfterBuyToken() public { address creatorAddress = makeAddr("creatorAddress"); address owner = erc20TokenEmitter.owner(); vm.startPrank(owner); erc20TokenEmitter.setCreatorsAddress(creatorAddress); erc20TokenEmitter.setCreatorRateBps(1000); // set the creator rate to be 10% erc20TokenEmitter.setEntropyRateBps(500); // 5% direct payment vm.stopPrank(); address user = makeAddr("someUser"); vm.deal(user, 10 ether); address[] memory recipients = new address[](1); recipients[0] = address(1); uint256[] memory bps = new uint256[](1); bps[0] = 10_000; vm.startPrank(user); erc20TokenEmitter.buyToken{ value: 10 ether}( recipients, bps, IERC20TokenEmitter.ProtocolRewardAddresses({ builder: address(0), purchaseReferral: address(0), deployer: address(0) }) ); vm.stopPrank(); console.log(address(erc20TokenEmitter).balance); // 0.9262 ether is left in the contract assert(address(erc20TokenEmitter).balance != 0); }
In this test we see that after executing the buyToken
function there is still ether left in the contract that cannot be withdrawn. This happens due to the issue I described above.
Ether stuck in ERC20TokenEmitter
Manual Review, VS Code, Foundry
To mitigate this issue I recommend adding a withdraw function in the ERC20TokenEmitter
contract to transfer any leftover ether to the treasury/DAO.
ETH-Transfer
#0 - c4-pre-sort
2023-12-22T01:23:51Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-12-22T01:24:04Z
raymondfam marked the issue as duplicate of #13
#2 - c4-pre-sort
2023-12-24T02:55:06Z
raymondfam marked the issue as duplicate of #406
#3 - c4-judge
2024-01-05T23:07:23Z
MarioPoneder marked the issue as satisfactory