0

For testing/mocking purposes, I need to call a contract (A) from another contract (B) that just forwards the calls to (A).

The calls need to be forwarded, not delegated. And I don't want to rewrite every function of A in B, but instead use something generic.

My first approach was taking OpenZeppelin's (transparent) Proxy contract, @openzeppelin/contracts/proxy/Proxy.sol and replace delegatecall with call.


  /**
   * @dev Delegates the current call to `implementation`.
   *
   * This function does not return to its internall call site, it will return directly to the external caller.
   */
  function _delegate(address implementation) internal virtual {
    assembly {
      // Copy msg.data. We take full control of memory in this inline assembly
      // block because it will not return to Solidity code. We overwrite the
      // Solidity scratch pad at memory position 0.
      calldatacopy(0, 0, calldatasize())

      // Call the implementation.
      // out and outsize are 0 because we don't know the size yet.
      // let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
      let result := call(gas(), implementation, 0, calldatasize(), 0, 0)

      // Copy the returned data.
      returndatacopy(0, 0, returndatasize())

      switch result
      // delegatecall returns 0 on error.
      case 0 {
        revert(0, returndatasize())
      }
      default {
        return(0, returndatasize())
      }
    }
  }

But it doesn't compiles, I get TypeError: Function "call" expects 7 arguments but got 6

Do you know the parameters of function call in assembly mode, or how can I implemented this forward non-transparent proxy?

4
  • the parameters to call() are described in Solidity Inline Assembly documentation page
    – Nulik
    Aug 26 '21 at 0:13
  • Thanks @Nulik for you help with call() documentation. Aug 26 '21 at 15:31
  • I made this change: // let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) by let result := call(gas(), implementation, 0, 0, calldatasize(), 0, 0) And it's working fine. Aug 27 '21 at 11:27
0

Finally I found the solution replacing delegatecall with call.

Solution (inheriting from OpenZeppelin's Proxy)

//SPDX-License-Identifier: Apache-2.0

pragma solidity ^0.8.0;

import {Proxy} from "@openzeppelin/contracts/proxy/Proxy.sol";

/**
 * @dev This contract provides a fallback function that forwards all calls to another contract using the EVM
 * instruction `call`.
 *
 * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
 * different contract through the {_delegate} function.
 *
 * The success and return data of the delegated call will be returned back to the caller of the proxy.
 */
contract ForwardProxy is Proxy {
  address internal _forwardTo;

  constructor(address forwardTo) {
    _forwardTo = forwardTo;
  }

  /**
   * @dev Delegates the current call to `implementation`.
   *
   * This function does not return to its internall call site, it will return directly to the external caller.
   */
  function _delegate(address implementation) internal virtual override {
    // solhint-disable-next-line no-inline-assembly
    assembly {
      // Copy msg.data. We take full control of memory in this inline assembly
      // block because it will not return to Solidity code. We overwrite the
      // Solidity scratch pad at memory position 0.
      calldatacopy(0, 0, calldatasize())

      // Call the implementation.
      // out and outsize are 0 because we don't know the size yet.
      // let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
      let result := call(gas(), implementation, 0, 0, calldatasize(), 0, 0)

      // Copy the returned data.
      returndatacopy(0, 0, returndatasize())

      switch result
      // delegatecall returns 0 on error.
      case 0 {
        revert(0, returndatasize())
      }
      default {
        return(0, returndatasize())
      }
    }
  }

  /**
   * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function
   * and {_fallback} should delegate.
   */
  function _implementation() internal view virtual override returns (address) {
    return _forwardTo;
  }

  function setForwardTo(address forwardTo) external {
    _forwardTo = forwardTo;
  }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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