Search code examples
ethereumsolidity

Solidity: Events triggered but state not updated after delegatecall


I would like to use delegatecall to deposit ETH into WETH and batch this process together with other actions (see code below). When running truffle tests, it seems like the events are triggered correctly but the state does not seem to be updated.

Same behavior on the Rinkeby network. The weird thing is that the transaction completes completely successfully. Here is an example of transaction: https://rinkeby.etherscan.io/tx/0x93c724174e2b70f2257544ebc0e0b9da6e31a7a752872da7683711bd4d4bd92b

Solidity code:

pragma solidity 0.4.24;

import "../interfaces/ERC20.sol";
import "./WETH9.sol";

contract SetupAccount {

address public exchangeAddress;
address public wethAddress;

constructor (
    address _exchangeAddress,
    address _wethAddress
) public {
    exchangeAddress = _exchangeAddress;
    wethAddress = _wethAddress;
}


function setup(
    address[] _tokenAddresses, 
    uint256[] _values
) public payable {
    for (uint i = 0; i < _tokenAddresses.length; i++) {
        _tokenAddresses[i].delegatecall(abi.encodeWithSignature("approve(address,uint256)", exchangeAddress, _values[i]));
    }

    if (msg.value != 0) {
        wethAddress.delegatecall(abi.encodeWithSignature("deposit()"));
    }
}

Failing truffle test:

  describe('setupAccount', async () => {
    beforeEach(async () => {
      weth = await WETH.new()
      exchange = await Exchange.new(rewardAccount)
      bnb = await BNB.new(user1, 1000)
      dai = await DAI.new(user1, 1000)
      omg = await OMG.new(user1, 1000)
      setupAccount = await SetupAccount.new(exchange.address, weth.address)
    })

    it('setupAccount should deposit weth and approve tokens', async () => {
      await setupAccount.setup(
        [bnb.address, dai.address, omg.address],
        [1000, 1000, 1000],
        { from: user1, value: 10 ** 18 }
      )

      let wethBalance = await weth.balanceOf(user1)

      wethBalance.should.be.bignumber.equal(10 ** 18)
      bnbAllowance.should.be.bignumber.equal(1000)
      daiAllowance.should.be.bignumber.equal(1000)
      omgAllowance.should.be.bignumber.equal(1000)
    })
  })

Events emitted during test:

Events emitted during test:
---------------------------

Deposit(dst: <indexed>, wad: 1000000000000000000)

---------------------------

Test result:

  1) Contract: SetupAccount
       setupAccount
         setupAccount should deposit weth and approve tokens:

      AssertionError: expected '0' to equal '1000000000000000000'
      + expected - actual

      -0
      +1000000000000000000

I'm confused as to why this doesn't work. Thank's in advance.


Solution

  • msg.value is preserved correctly through a delegatecall. The following code demonstrates this, as evidenced by the emitted event showing the correct value.

    pragma solidity >0.4.99 <0.6;
    
    contract Target {
        event Received(uint256 amount);
    
        function deposit() external payable {
            emit Received(msg.value);
        }
    }
    
    contract Delegater {
        function deposit() external payable {
            (bool success,) = address(new Target()).delegatecall(abi.encodeWithSignature("deposit()"));
            require(success);
        }
    }