Skip to content

delegatecall broken after 1.9.2 #20136

@sadoci

Description

@sadoci

It seems delegatecall is broken between 1.9.1 and 1.9.2. Attached code works in 1.9.1, but does not in 1.9.2.

System information

Version: 1.9.2-stable
Git Commit: e76047e9f5499b58064bddde514dd3119a090adf
Architecture: amd64
Protocol Versions: [63]
Network Id: 1
Go Version: go1.12
Operating System: linux

Expected behaviour

delegatecall should work, of course.

Actual behaviour

delegatecall fails with "out of gas" error.

Steps to reproduce the behaviour

With private geth node and the o_o.js below, in geth console,

personal.unlockAccount(eth.accounts[0], '<password here>', 360000)
loadScript('o_o.js')
inner = Inner_new()
outer = Outer_new()
miner.start(1); admin.sleepBlocks(1); miner.stop()
outer.setImplementation(inner.address, {from:eth.accounts[0]})
tmp = web3.eth.contract(Inner_contract.abi).at(outer.address)
miner.start(1); admin.sleepBlocks(1); miner.stop()

tmp.value()
tx = tmp.set(100, {from:eth.accounts[0], gas:100000})
miner.start(1); admin.sleepBlocks(1); miner.stop()
tmp.value()

debug.traceTransaction(tx)

Backtrace

debug.traceTransaction(<transaction hash>) in 1.9.2 shows that "DELEGATECALL" mishandles gasCost as shown below.

... {
      depth: 1,
      error: {},
      gas: 78152,
      gasCost: 78852,
      memory: ["0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000080", "0000000000000000000000000000000000000000000000000000000000000000", "60fe47b100000000000000000000000000000000000000000000000000000000", "0000006400000000000000000000000000000000000000000000000000000000"],
      op: "DELEGATECALL",
      pc: 128,
      stack: ["0000000000000000000000000000000000000000000000000000000060fe47b1", "0000000000000000000000003bc5d3aa07701b08bd11bdc1d41fb44cefb355f9", "0000000000000000000000000000000000000000000000000000000000000080", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000024", "0000000000000000000000000000000000000000000000000000000000000080", "0000000000000000000000003bc5d3aa07701b08bd11bdc1d41fb44cefb355f9", "0000000000000000000000000000000000000000000000000000000000013148"],
      storage: {}
  }]

In 1.9.1, it gets handled properly.

{
      depth: 1,
      gas: 78152,
      gasCost: 76942,
      memory: ["0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000080", "0000000000000000000000000000000000000000000000000000000000000000", "60fe47b100000000000000000000000000000000000000000000000000000000", "0000006400000000000000000000000000000000000000000000000000000000"],
      op: "DELEGATECALL",
      pc: 128,
      stack: ["0000000000000000000000000000000000000000000000000000000060fe47b1", "0000000000000000000000003bc5d3aa07701b08bd11bdc1d41fb44cefb355f9", "0000000000000000000000000000000000000000000000000000000000000080", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000024", "0000000000000000000000000000000000000000000000000000000000000080", "0000000000000000000000003bc5d3aa07701b08bd11bdc1d41fb44cefb355f9", "0000000000000000000000000000000000000000000000000000000000013148"],      storage: {}
  }

Code in solidity and .js format

o_o.sol

pragma solidity ^0.4.24;

// modified from "github.com:/OpenZeppelin/openzeppelin-labs/upgradeability_using_eternal_storage/contracts/Proxy.sol"
contract Proxy {
    address _impl;

    constructor() public {}

    function () public payable {
        address addr = _impl;
        require(_impl != address(0));

        assembly {
            let ptr := mload(0x40)
            calldatacopy(ptr, 0, calldatasize)
            let result := delegatecall(gas, addr, ptr, calldatasize, 0, 0)
            let size := returndatasize
            returndatacopy(ptr, 0, size)

            switch result
            case 0 { revert(ptr, size) }
            default { return(ptr, size) }
        }
    }

    function setImplementation(address newImpl) public {
        require(newImpl != address(0), "newImplementation should be non-zero");
        _impl = newImpl;
    }

    function implementation() public view returns (address) {
        return _impl;
    }
}

contract Outer is Proxy {
    constructor() public {}
}

contract Inner is Proxy{
    uint public value;

    function set(uint v) public {
        value = v;
    }
}

o_o.js

var Inner_data = "0x6080604052610290806100136000396000f3006080604052600436106100615763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416633fa4f24581146100ab5780635c60da1b146100d257806360fe47b114610110578063d784d4261461012a575b60005473ffffffffffffffffffffffffffffffffffffffff1680151561008657600080fd5b60405136600082376000803683855af43d806000843e8180156100a7578184f35b8184fd5b3480156100b757600080fd5b506100c0610158565b60408051918252519081900360200190f35b3480156100de57600080fd5b506100e761015e565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561011c57600080fd5b5061012860043561017a565b005b34801561013657600080fd5b5061012873ffffffffffffffffffffffffffffffffffffffff6004351661017f565b60015481565b60005473ffffffffffffffffffffffffffffffffffffffff1690565b600155565b73ffffffffffffffffffffffffffffffffffffffff8116151561022857604080517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f6e6577496d706c656d656e746174696f6e2073686f756c64206265206e6f6e2d60448201527f7a65726f00000000000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b6000805473ffffffffffffffffffffffffffffffffffffffff191673ffffffffffffffffffffffffffffffffffffffff929092169190911790555600a165627a7a72305820e2312556d728efd4b50ba1f9a883053dbe2302e1c03909adae101584810704bd0029";
var Inner_contract = web3.eth.contract([{"constant":true,"inputs":[],"name":"value","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"implementation","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"v","type":"uint256"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newImpl","type":"address"}],"name":"setImplementation","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"}]);

function Inner_new() {
  return Inner_contract.new(
  {
    from: web3.eth.accounts[0],
    data: Inner_data,
    gas: "0x10000000"
  }, function (e, contract) {
    if (typeof contract.address !== "undefined") {
      console.log("Contract mined! address: " + contract.address + " transactionHash: " + contract.transactionHash);
    }
  });
}

function Inner_load(addr) {
   return Inner_contract.at(addr);
}
var Outer_data = "0x608060405234801561001057600080fd5b50610230806100206000396000f30060806040526004361061004b5763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416635c60da1b8114610095578063d784d426146100d3575b60005473ffffffffffffffffffffffffffffffffffffffff1680151561007057600080fd5b60405136600082376000803683855af43d806000843e818015610091578184f35b8184fd5b3480156100a157600080fd5b506100aa610103565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b3480156100df57600080fd5b5061010173ffffffffffffffffffffffffffffffffffffffff6004351661011f565b005b60005473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff811615156101c857604080517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f6e6577496d706c656d656e746174696f6e2073686f756c64206265206e6f6e2d60448201527f7a65726f00000000000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b6000805473ffffffffffffffffffffffffffffffffffffffff191673ffffffffffffffffffffffffffffffffffffffff929092169190911790555600a165627a7a7230582083dd011c230446c2cc9925ebe5be1ab6101ff5452dd31dc4449bcc8012668d700029";
var Outer_contract = web3.eth.contract([{"constant":true,"inputs":[],"name":"implementation","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newImpl","type":"address"}],"name":"setImplementation","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"}]);

function Outer_new() {
  return Outer_contract.new(
  {
    from: web3.eth.accounts[0],
    data: Outer_data,
    gas: "0x10000000"
  }, function (e, contract) {
    if (typeof contract.address !== "undefined") {
      console.log("Contract mined! address: " + contract.address + " transactionHash: " + contract.transactionHash);
    }
  });
}

function Outer_load(addr) {
   return Outer_contract.at(addr);
}
var Proxy_data = "0x608060405234801561001057600080fd5b50610230806100206000396000f30060806040526004361061004b5763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416635c60da1b8114610095578063d784d426146100d3575b60005473ffffffffffffffffffffffffffffffffffffffff1680151561007057600080fd5b60405136600082376000803683855af43d806000843e818015610091578184f35b8184fd5b3480156100a157600080fd5b506100aa610103565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b3480156100df57600080fd5b5061010173ffffffffffffffffffffffffffffffffffffffff6004351661011f565b005b60005473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff811615156101c857604080517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f6e6577496d706c656d656e746174696f6e2073686f756c64206265206e6f6e2d60448201527f7a65726f00000000000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b6000805473ffffffffffffffffffffffffffffffffffffffff191673ffffffffffffffffffffffffffffffffffffffff929092169190911790555600a165627a7a72305820388ccb391aae6d68a74b734b2810658d77ebed9bc231214a8912a652ea64ae700029";
var Proxy_contract = web3.eth.contract([{"constant":true,"inputs":[],"name":"implementation","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newImpl","type":"address"}],"name":"setImplementation","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"}]);

function Proxy_new() {
  return Proxy_contract.new(
  {
    from: web3.eth.accounts[0],
    data: Proxy_data,
    gas: "0x10000000"
  }, function (e, contract) {
    if (typeof contract.address !== "undefined") {
      console.log("Contract mined! address: " + contract.address + " transactionHash: " + contract.transactionHash);
    }
  });
}

function Proxy_load(addr) {
   return Proxy_contract.at(addr);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions