Revolution Protocol - hakymulla's results

A protocol to empower communities to raise funds, fairly distribute governance, and maximize their impact in the world.

General Information

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

Collective

Findings Distribution

Researcher Performance

Rank: 67/110

Findings: 1

Award: $44.03

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

44.0266 USDC - $44.03

Labels

bug
3 (High Risk)
satisfactory
sufficient quality report
duplicate-210

External Links

Lines of code

https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/ERC20TokenEmitter.sol#L152

Vulnerability details

Impact

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.

Proof of Concept

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");
        
    }

Tools Used

Foundry

A refund or withdraw function in erc20TokenEmitter.sol to distribute the ETH to the appropriate person decided by the protocol.

Assessed type

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

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter