Search code examples
structethereumsolidity

Struct on delegatecall as an argument


Is it possible to pass a struct as an argument to delegatecall ?

I have this function that calls delegatecall and takes a struct (a 0x quote) as an argument which is later used on the function signature and in the proper call:

function executeDelegate(address _weth, address _contract, ZrxQuote memory _zrxQuote) private returns(uint, string memory) {
        console.log('spender address: ', _zrxQuote.spender); //----> testing
        (bool success, ) = logicContract.delegatecall(
                abi.encodeWithSignature('execute(address,address,uint256,ZrxQuote)', _weth, _contract, borrowed, _zrxQuote)
        );
        console.log(success);
        require(success, 'Delegate Call failed');
        return (0, '');
    }

...but it doesn't work and returns false every time and the error Delegate Call failed.

I have this console.log('spender address: ', _zrxQuote.spender); to test if my struct is being read successfully and it is.

Also, if I remove the struct entirely of the equation (from the function, from delegatecall, from the call, from the logic contract), delegatecall works perfectly, something like:

function executeDelegate(address _weth, address _contract) private returns(uint, string memory) {
        (bool success, ) = logicContract.delegatecall(
                abi.encodeWithSignature('execute(address,address,uint256)', _weth, _contract, borrowed)
        );
        require(success, 'Delegate Call failed');
        return (0, '');
    }

So the problem is directly with the struct being passed to delegatecall, but I can't seem to find on any docs what the issue is (storage variables are the same).

These are the contracts:

Proxy:

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.5.0;
pragma experimental ABIEncoderV2;

import "@studydefi/money-legos/dydx/contracts/DydxFlashloanBase.sol";
import "@studydefi/money-legos/dydx/contracts/ICallee.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "hardhat/console.sol";


contract DydxFlashloaner is ICallee, DydxFlashloanBase {
    struct ZrxQuote {
        address sellTokenAddress;
        address buyTokenAddress;
        address spender;
        address swapTarget;
        bytes swapCallData;
    }

    struct MyCustomData {
        address token;
        uint256 repayAmount;
    } 

    address public logicContract;
    uint public borrowed;

    constructor(address _logicContract, uint _borrowed) public {
        logicContract = _logicContract;
        borrowed = _borrowed;
    }

/******* Part that matters ******/

    function callFunction(
        address sender,
        Account.Info memory account,
        bytes memory data
    ) public {
        (MyCustomData memory mcd, ZrxQuote memory zrx) = abi.decode(data, (MyCustomData, ZrxQuote));
        uint256 balOfLoanedToken = IERC20(mcd.token).balanceOf(address(this));

        require(
            balOfLoanedToken >= mcd.repayAmount,
            "Not enough funds to repay dydx loan!"
        );
        
        executeDelegate(mcd.token, address(this), zrx); //----> calls delegatecall
    }


    function executeDelegate(address _weth, address _contract, ZrxQuote memory _zrxQuote) private returns(uint, string memory) {
        console.log('this is: ', _zrxQuote.spender);
        (bool success, ) = logicContract.delegatecall(
                abi.encodeWithSignature('execute(address,address,uint256,ZrxQuote)', _weth, _contract, borrowed, _zrxQuote)
        );
        console.log(success);
        require(success, 'Delegate Call failed');
        return (0, '');
    }

/******* End ******/

    function initiateFlashLoan(
        address _solo, 
        address _token, 
        uint256 _amount, 
        address[] calldata _quoteAddr, 
        bytes calldata _quoteData
    ) external
    {
        ZrxQuote memory zrxQuote = ZrxQuote({
            sellTokenAddress: _quoteAddr[0],
            buyTokenAddress: _quoteAddr[1],
            spender: _quoteAddr[2],
            swapTarget: _quoteAddr[3],
            swapCallData: _quoteData
        });


        ISoloMargin solo = ISoloMargin(_solo);

        uint256 marketId = _getMarketIdFromTokenAddress(_solo, _token);

        uint256 repayAmount = _getRepaymentAmountInternal(_amount);
        IERC20(_token).approve(_solo, repayAmount);

        Actions.ActionArgs[] memory operations = new Actions.ActionArgs[](3);

        operations[0] = _getWithdrawAction(marketId, _amount);
        operations[1] = _getCallAction(
            abi.encode(MyCustomData({token: _token, repayAmount: repayAmount}), zrxQuote)
        );
        operations[2] = _getDepositAction(marketId, repayAmount);

        Account.Info[] memory accountInfos = new Account.Info[](1);
        accountInfos[0] = _getAccountInfo();

        solo.operate(accountInfos, operations);
    }
}

Logic:

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
pragma abicoder v2; //tried with pragma experimental ABIEncoderV2 also

import '@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol';
import './interfaces/MyILendingPool.sol';
import './interfaces/MyIERC20.sol';
import "hardhat/console.sol";

contract FlashLoaner {
    struct ZrxQuote {
        address sellTokenAddress;
        address buyTokenAddress;
        address spender;
        address swapTarget;
        bytes swapCallData;
    }

    struct MyCustomData {
        address token;
        uint256 repayAmount;
    }

    address public logicContract;
    uint public borrowed;


    function execute(address _weth, address _contract, uint256 _borrowed, ZrxQuote memory _zrxQuote) public {
        console.log('hello');
     
   //I removed the code for simplicity, but it never executes, not even the 'hello'.
        
    }

Thanks for the help!


Solution

  • Solution:

    Have to pass a tuple instead to abi.encodeWithSignature, according to the docs: https://docs.soliditylang.org/en/v0.8.6/abi-spec.html#mapping-solidity-to-abi-types

    So it would be:

    execute(address,address,uint256,(address, address, address, address, bytes))
    

    ...instead of :

    execute(address,address,uint256,ZrxQuote)