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: 66/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
https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/ERC20TokenEmitter.sol#L165 https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/ERC20TokenEmitter.sol#L173 https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/ERC20TokenEmitter.sol#L177
The buyToken
function on ERC20TokenEmitter.sol
enables anyone to purchase the ERC20 governance token anytime. A portion of the value spent on buying the ERC20 tokens is paid to protocol rewards contract. The value remaining after protocol rewards is shared among the treasury and creators. However, the sum of percentages used to calculate the shares does not equal to 100% which leads to the fact that it is guaranteed that on each call to the buyToken
function, a portion of msg.value
will be locked in the contract. The amount that will be locked depends on 2 protocol parameters (creatorRate
and entropyRate
) and the ETH value sent by the buyer. The higher the creatorRate
and the lower the entropyRate
, the higher the amount of ETH locked in the contract. The amount locked can even attend +90% of the total value sent by the buyer as demonstrated in the PoC section. The current implementation has no mechanism for refunding the locked value.
The following test confirms the fact that a high amount of ETH sent by the buyer can be locked. The test demonstrates that +90% of ETH sent by the buyer will be locked inside the smart contract if the following protocol parameters are set:
creatorRate
= 9530entropyRate
= 96
with 0.0.0000028 ETH
as msg.value sent by the buyerThese parameter values were found through the Fuzzing test.
function testMostOfMsgValueCanBeLockedWhenBuyingToken() public { uint valueToSend = 2814749767106; // ~ 0.0.0000028 ETH uint creatorRate = 9530; uint entropyRate = 96; 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 erc20TokenEmitter.setCreatorsAddress(address(80)); // Perform token purchase vm.startPrank(address(this)); erc20TokenEmitter.buyToken{ value: valueToSend }( recipients, bps, IERC20TokenEmitter.ProtocolRewardAddresses({ builder: address(0), purchaseReferral: address(0), deployer: address(0) }) ); // audit-danger +90% of value sent by the buyer will be locked!! assertGt(address(erc20TokenEmitter).balance , (valueToSend * 90)/100); }
Running 1 test for test/token-emitter/ERC20TokenEmitter.t.sol:ERC20TokenEmitterTest [PASS] testMostOfMsgValueCanBeLockedWhenBuyingToken() (gas: 489894) Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.85ms Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)
Manual Analysis, Fuzz testing
Consider refunding the excess ETH at the end of the transaction. Below is a suggestion of an updated code of the buyToken
function:
function buyToken( ... //Transfer ETH to treasury and update emitted emittedTokenWad += totalTokensForBuyers; if (totalTokensForCreators > 0) emittedTokenWad += totalTokensForCreators; //Deposit funds to treasury (bool success, ) = treasury.call{ value: toPayTreasury }(new bytes(0)); require(success, "Transfer failed."); //Transfer ETH to creators if (creatorDirectPayment > 0) { (success, ) = creatorsAddress.call{ value: creatorDirectPayment }(new bytes(0)); require(success, "Transfer failed."); } // audit-fix get the excessEth and refund it uint excessEth = msgValueRemaining - toPayTreasury - creatorDirectPayment; refund(remainingETH);
Payable
#0 - c4-pre-sort
2023-12-22T02:34:58Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-12-22T02:35:09Z
raymondfam marked the issue as duplicate of #13
#2 - c4-pre-sort
2023-12-24T02:55:07Z
raymondfam marked the issue as duplicate of #406
#3 - c4-judge
2024-01-05T23:07:46Z
MarioPoneder marked the issue as satisfactory