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: 67/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
In some cases after buying token using the ERC20TokenEmitter::buyToken
function, after protocolRewardsRecipients, treasury, creatorsAddress has been paid, There remains surplus ETH trapped in ERC20TokenEmitter contract with no function to withdraw. Especially when ERC20TokenEmitter::entropyRateBps
is less than 10000.
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
Copy the code below in ERC20TokenEmitter.t.sol.
function testSurplusETHTrappedInContractPOC() public { uint256 creatorRate = 9000; uint256 entropyRate = 500; uint256 valueToSend = 10 ether; vm.startPrank(address(dao)); // Set creator and entropy rates erc20TokenEmitter.setCreatorRateBps(creatorRate); erc20TokenEmitter.setEntropyRateBps(entropyRate); assertEq(erc20TokenEmitter.creatorRateBps(), creatorRate, "Creator rate not set correctly"); assertEq(erc20TokenEmitter.entropyRateBps(), entropyRate, "Entropy rate not set correctly"); // Setup for buying token address[] memory recipients = new address[](1); recipients[0] = address(1); // recipient address uint256[] memory bps = new uint256[](1); bps[0] = 10000; // 100% of the tokens to the recipient // uint256 valueToSend = 49 ether; erc20TokenEmitter.setCreatorsAddress(address(80)); address creatorsAddress = erc20TokenEmitter.creatorsAddress(); uint256 creatorsInitialEthBalance = address(erc20TokenEmitter.creatorsAddress()).balance; uint256 feeAmount = erc20TokenEmitter.computeTotalReward(valueToSend); // Calculate expected ETH sent to creator uint256 totalPaymentForCreator = ((valueToSend - feeAmount) * creatorRate) / 10000; uint256 expectedCreatorEth = (totalPaymentForCreator * entropyRate) / 10000; if (creatorRate == 0 || entropyRate == 10_000) vm.expectRevert("Ether amount must be greater than 0"); uint256 expectedCreatorTokens = uint( erc20TokenEmitter.getTokenQuoteForEther(totalPaymentForCreator - expectedCreatorEth) ); // Perform token purchase vm.startPrank(address(this)); uint256 tokensSold = erc20TokenEmitter.buyToken{ value: valueToSend }( recipients, bps, IERC20TokenEmitter.ProtocolRewardAddresses({ builder: address(0), purchaseReferral: address(0), deployer: address(0) }) ); uint256 creatorTokenBalance = erc20TokenEmitter.balanceOf(erc20TokenEmitter.creatorsAddress()); assertEq(creatorTokenBalance, expectedCreatorTokens, "Creator did not receive correct amount of tokens"); uint256 creatorsNewEthBalance = address(erc20TokenEmitter.creatorsAddress()).balance; assertEq( creatorsNewEthBalance - creatorsInitialEthBalance, expectedCreatorEth, "Incorrect ETH amount sent to creator" ); // Verify tokens distributed to recipient uint256 recipientTokenBalance = erc20TokenEmitter.balanceOf(address(1)); assertEq(recipientTokenBalance, tokensSold, "Recipient did not receive correct amount of tokens"); emit Log("Emitter: ",address(erc20TokenEmitter).balance); assertEq(address(erc20TokenEmitter).balance, 0, "No Ether Should remain in Emitter contract"); }
The Above Test leaves about 8.33625 Eth trapped in the erc20TokenEmitter contract which is about 83% of the ETH sent.
Also Fuzz Test
function testFuzzingSurplusETHTrappedInContractPOC(uint256 creatorRate, uint256 entropyRate, uint256 valueToSend) public { // Assume valid rates vm.assume(creatorRate <= 10000 && entropyRate <= 10000); vm.assume(valueToSend > 0.0000001 ether && valueToSend < 50_000 ether); vm.startPrank(address(dao)); erc20TokenEmitter.setCreatorRateBps(creatorRate); erc20TokenEmitter.setEntropyRateBps(entropyRate); assertEq(erc20TokenEmitter.creatorRateBps(), creatorRate, "Creator rate not set correctly"); assertEq(erc20TokenEmitter.entropyRateBps(), entropyRate, "Entropy rate not set correctly"); // Setup for buying token address[] memory recipients = new address[](1); recipients[0] = address(1); // recipient address uint256[] memory bps = new uint256[](1); bps[0] = 10000; // 100% of the tokens to the recipient // uint256 valueToSend = 49 ether; erc20TokenEmitter.setCreatorsAddress(address(80)); address creatorsAddress = erc20TokenEmitter.creatorsAddress(); uint256 creatorsInitialEthBalance = address(erc20TokenEmitter.creatorsAddress()).balance; uint256 feeAmount = erc20TokenEmitter.computeTotalReward(valueToSend); // Calculate expected ETH sent to creator uint256 totalPaymentForCreator = ((valueToSend - feeAmount) * creatorRate) / 10000; uint256 expectedCreatorEth = (totalPaymentForCreator * entropyRate) / 10000; if (creatorRate == 0 || entropyRate == 10_000) vm.expectRevert("Ether amount must be greater than 0"); uint256 expectedCreatorTokens = uint( erc20TokenEmitter.getTokenQuoteForEther(totalPaymentForCreator - expectedCreatorEth) ); // Perform token purchase vm.startPrank(address(this)); uint256 tokensSold = erc20TokenEmitter.buyToken{ value: valueToSend }( recipients, bps, IERC20TokenEmitter.ProtocolRewardAddresses({ builder: address(0), purchaseReferral: address(0), deployer: address(0) }) ); uint256 creatorTokenBalance = erc20TokenEmitter.balanceOf(erc20TokenEmitter.creatorsAddress()); assertEq(creatorTokenBalance, expectedCreatorTokens, "Creator did not receive correct amount of tokens"); uint256 creatorsNewEthBalance = address(erc20TokenEmitter.creatorsAddress()).balance; assertEq( creatorsNewEthBalance - creatorsInitialEthBalance, expectedCreatorEth, "Incorrect ETH amount sent to creator" ); uint256 recipientTokenBalance = erc20TokenEmitter.balanceOf(address(1)); assertEq(recipientTokenBalance, tokensSold, "Recipient did not receive correct amount of tokens"); emit Log("Emitter: ",address(erc20TokenEmitter).balance); assertEq(address(erc20TokenEmitter).balance, 0, "No Ether Should remain in Emitter contract"); }
Foundry
A refund or withdraw function in erc20TokenEmitter.sol to distribute the ETH to the appropriate person decided by the protocol.
Other
#0 - c4-pre-sort
2023-12-22T03:01:42Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-12-22T03:01:49Z
raymondfam marked the issue as duplicate of #13
#2 - c4-pre-sort
2023-12-24T02:55:09Z
raymondfam marked the issue as duplicate of #406
#3 - c4-judge
2024-01-05T23:08:34Z
MarioPoneder marked the issue as satisfactory