Report #29338

Report Date
March 13, 2024

shift function allows 1000 bean to be traded for 999 eth . all funds can be stolen

Report Info

Report ID


Report type

Smart Contract

Has PoC?




Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield


This is a critical bug whereby all funds can be stolen from the contract .

The issue is with the shift function . The bug is very bad. If I transfer lets say 1000 bean tokens , and then call the shift function , what will happen is that the function transfer at up to 99.99 tokens back to me in weth . Of couse weth is far more valuable than the bean token , this means I get far more than what I have sent to the contract The function does not check that there are different values between tokens . In This is very dangerous this happens because in the function there is no checks that determines whether

n the poc I have provided as you can see I have transferred token 0 to the wells contract. I have then called the shift contract and stated I want a certain amount of token 1 in return . The contract than gives me a certain amount of token 1 to my address.

How to solve the bug , the way to solve the bug is to have a check in the shift function in regards to the tokenOut and then evaluate how much token a user should be sent from the wells contract depending on that tokens value . This bug is very bad and all funds can be stolen from the contract

Proof of concept

function test_shift_balanced_pool_amin() public prank(user) {
        Balances memory wellBalanceBeforeShift = getBalances(address(well), well);
     uint256 amount =  1000e18;
         tokens[1].transfer(address(well), 10e18);
        uint256 user_amount = tokens[0].balanceOf(user);
        uint256 user_before_token1= tokens[1].balanceOf(user);
        uint256 before_wells_amount0 =tokens[0].balanceOf(address(well));
         uint256 before_wells_amount1 =tokens[1].balanceOf(address(well));
        address _user = users.getNextUserAddress();

       well.shift(tokens[0],9.9e18, user);
          uint256 after_wells_amount0 =tokens[0].balanceOf(address(well));
         uint256 after_wells_amount1 =tokens[1].balanceOf(address(well));
        uint256 afteruser_amount = tokens[0].balanceOf(user);

         uint256 user_amount_token1_after = tokens[1].balanceOf(user);
        console.log("USER BALANCE TOKEN 0 BEFORE ",user_amount );
          console.log("USER BALANCE TOKEN 0 AFTER ",afteruser_amount);

         console.log("USER TOKEN1 BALANCE BEFORE : =>",user_before_token1);
        console.log("USER TOKEN1 BALANCE AFTER  : =>" ,user_amount_token1_after);
          console.log("the balance of wells before for token 0 is ",before_wells_amount0);
          console.log("the balance of wells for token 1 after is ",after_wells_amount0);
             console.log("the balance of wells before for token 0 is ",before_wells_amount1 );
            console.log("the balance of wells for token 1 after is: => ",after_wells_amount1);
            console.log("the difference between before +after user token 0" , afteruser_amount - user_amount );
            console.log("the difference USER TOKEN 1 : =>", user_amount_token1_after-user_before_token1);
            console.log("diffrence wells token 0 before and after :=>",before_wells_amount0 - after_wells_amount0) ;
            console.log("differnce wells token 1 before and after ;>",before_wells_amount1 - after_wells_amount1);

BIC Response

This is not a valid bug report because it describes expected behavior of the shift function. The shift function accepts a minAmountOut parameter, which indicates the minimum amount of the output token that should be sent back to the user in order for the function call to succeed. At some point the input token must be sent to the Well before the shift function is called in order to be able to get any tokens out as a result of the shift function call.

Due to these reasons, we are closing the submission and no reward will be issued.