2

I have a payable function that takes an array of call datas as input, to which it delegate calls one-by-one:

function batch(bytes[] calldata calls) external payable override {
    uint256 count = calls.length;

    for (uint256 i = 0; i < count; ++i) {
        (bool success, bytes memory result) = address(this).delegatecall(calls[i]);
        if (!success) {
            revert Errors.BatchError(result);
        }
    }
}

Now, suppose that the initial call to batch has a non-zero ETH amount passed in msg.value. Would the initial call revert if one of the calls is made to a non-payable function?

1
  • 1
    I'd imagine delegatecall reverts just like normal calls - if you call a non-payable function with value, it fails. And with your code causes a revert, which reverts the whole transaction (could be perhaps avoided with some try-catching). But not sure. Commented Nov 21 at 14:49

1 Answer 1

2

Yes, if you delegatecall to a non-payable function after initially calling with a non-zero msg.value, the transaction will revert.

Use this example contract as a reference:

//SPDX-License-Identifier: MIT

pragma solidity 0.8.26;

error BatchError(bytes result);

contract DelegateCallNonPayable {

    uint public y;
    // If we send this function calldata for foo(uint x) and some eth (msg.value), will it revert?
    function batch(bytes[] calldata calls) external payable {
        uint256 count = calls.length;

        for (uint256 i = 0; i < count; ++i) {
            (bool success, bytes memory result) = address(this).delegatecall(calls[i]);
            if (!success) {
                revert BatchError(result);
            }
        }
    }

    // Non-payable function to be called
    function foo(uint x) public {
        y = x;
    }

    // Payable function to be called
    function payableFoo(uint x) public payable {
        y = x;
    }

    // Get input for `batch()`
    function bar(bool z) external pure returns(bytes memory) {
        if(z) {
            return abi.encodeWithSelector(this.foo.selector, 3);
        } else {
            return abi.encodeWithSelector(this.payableFoo.selector, 7);
        }
        
    }

}
  1. If you call batch() with zero as msg.value, the function will always succeed using either return data from bar().
  2. The transaction will still succeed if you use the call data for payableFoo() and use a non-zero msg.value.
  3. The transaction will fail if you use the call data for foo() and use a non-zero msg.value.

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.