2

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!

1 Answer 1

1

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)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.