Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Improved `delegatecall` proxy contract factory (Solidity) [v0.0.5]
/***
* Shoutouts:
*
* Bytecode origin https://www.reddit.com/r/ethereum/comments/6ic49q/any_assembly_programmers_willing_to_write_a/dj5ceuw/
* Modified version of Vitalik's https://www.reddit.com/r/ethereum/comments/6c1jui/delegatecall_forwarders_how_to_save_5098_on/
* Credits to Jorge Izquierdo (@izqui) for coming up with this design here: https://gist.github.com/izqui/7f904443e6d19c1ab52ec7f5ad46b3a8
* Credits to Stefan George (@Georgi87) for inspiration for many of the improvements from Gnosis Safe: https://github.com/gnosis/gnosis-safe-contracts
*
* This version has many improvements over the original @izqui's library like using REVERT instead of THROWing on failed calls.
* It also implements the awesome design pattern for initializing code as seen in Gnosis Safe Factory: https://github.com/gnosis/gnosis-safe-contracts/blob/master/contracts/ProxyFactory.sol
* but unlike this last one it doesn't require that you waste storage on both the proxy and the proxied contracts (v. https://github.com/gnosis/gnosis-safe-contracts/blob/master/contracts/Proxy.sol#L8 & https://github.com/gnosis/gnosis-safe-contracts/blob/master/contracts/GnosisSafe.sol#L14)
*
*
* v0.0.2
* The proxy is now only 60 bytes long in total. Constructor included.
* No functionalities were added. The change was just to make the proxy leaner.
*
* v0.0.3
* Thanks @dacarley for noticing the incorrect check for the subsequent call to the proxy. 🙌
* Note: I'm creating a new version of this that doesn't need that one call.
* Will add tests and put this in its own repository soon™.
*
* v0.0.4
* All the merit in this fix + update of the factory is @dacarley 's. 🙌
* Thank you! 😄
*
* v0.0.5
* Huge design problem was overwriting the last 9 bytes of you extra "_data" array with zeros! 😱
* If you had this deployed please redeploy the updated version.
*
* Potential updates can be found at https://gist.github.com/GNSPS/ba7b88565c947cfd781d44cf469c2ddb
*
***/
pragma solidity 0.4.19;
/* solhint-disable no-inline-assembly, indent, state-visibility, avoid-low-level-calls */
contract ProxyFactory {
event ProxyDeployed(address proxyAddress, address targetAddress);
event ProxiesDeployed(address[] proxyAddresses, address targetAddress);
function createManyProxies(uint256 _count, address _target, bytes _data)
external
{
address[] memory proxyAddresses = new address[](_count);
for (uint256 i = 0; i < _count; ++i) {
proxyAddresses[i] = createProxyImpl(_target, _data);
}
ProxiesDeployed(proxyAddresses, _target);
}
function createProxy(address _target, bytes _data)
external
returns (address proxyContract)
{
proxyContract = createProxyImpl(_target, _data);
ProxyDeployed(proxyContract, _target);
}
function createProxyImpl(address _target, bytes _data)
internal
returns (address proxyContract)
{
assembly {
let contractCode := mload(0x40) // Find empty storage location using "free memory pointer"
mstore(add(contractCode, 0x0b), _target) // Add target address, with a 11 bytes [i.e. 23 - (32 - 20)] offset to later accomodate first part of the bytecode
mstore(sub(contractCode, 0x09), 0x000000000000000000603160008181600b9039f3600080808080368092803773) // First part of the bytecode, shifted left by 9 bytes, overwrites left padding of target address
mstore(add(contractCode, 0x2b), 0x5af43d828181803e808314602f57f35bfd000000000000000000000000000000) // Final part of bytecode, offset by 43 bytes
proxyContract := create(0, contractCode, 60) // total length 60 bytes
if iszero(extcodesize(proxyContract)) {
revert(0, 0)
}
// check if the _data.length > 0 and if it is forward it to the newly created contract
let dataLength := mload(_data)
if iszero(iszero(dataLength)) {
if iszero(call(gas, proxyContract, 0, add(_data, 0x20), dataLength, 0, 0)) {
revert(0, 0)
}
}
}
}
}
/***
*
* PROXY contract (bytecode)
603160008181600b9039f3600080808080368092803773f00df00df00df00df00df00df00df00df00df00d5af43d828181803e808314602f57f35bfd
*
* PROXY contract (opcodes)
0 PUSH1 0x31
2 PUSH1 0x00
4 DUP2
5 DUP2
6 PUSH1 0x0b
8 SWAP1
9 CODECOPY
10 RETURN
11 PUSH1 0x00
13 DUP1
14 DUP1
15 DUP1
16 DUP1
17 CALLDATASIZE
18 DUP1
19 SWAP3
20 DUP1
21 CALLDATACOPY
22 PUSH20 0xf00df00df00df00df00df00df00df00df00df00d
43 GAS
44 DELEGATECALL
45 RETURNDATASIZE
46 DUP3
47 DUP2
48 DUP2
49 DUP1
50 RETURNDATACOPY
51 DUP1
52 DUP4
53 EQ
54 PUSH1 0x2f
56 JUMPI
57 RETURN
58 JUMPDEST
59 REVERT
*
***/
@ethers

This comment has been minimized.

Copy link

commented Mar 3, 2018

add tests and put on a repo?

@GNSPS

This comment has been minimized.

Copy link
Owner Author

commented Mar 5, 2018

Let's do it! 😄

@chriseth

This comment has been minimized.

Copy link

commented Mar 6, 2018

Most of the push1 0x00 instructions can be replaced by a single push1 0x00 in the beginning and dups. Note that psuh1 0x00 is two bytes long!

Also the final part of the bytecode could be improved by pushing the zero before the jump and then using a swap.

@GNSPS

This comment has been minimized.

Copy link
Owner Author

commented Mar 8, 2018

I think I optimized it to the max, now.

Thank you @chriseth! 😄

Could you take a second look to see if you still see any room for improvement?

@dacarley

This comment has been minimized.

Copy link

commented Mar 17, 2018

Nit: On line 30, the comment is wrong. You're offsetting by 43 bytes (which is correct), but the comment says 42.

@dacarley

This comment has been minimized.

Copy link

commented Mar 17, 2018

Also, there's a bug in the line that checks the result of the call when there's data attached. call returns 1 on success, so as it's currently written, it will revert on any successful call.

I've modified my local copy to read:

            let dataLength := mload(_data) 
            if iszero(iszero(dataLength)) {
                if iszero(call(gas, proxyContract, 0, add(_data, 0x20), dataLength, 0, 0)) {
                    revert(0, 0)
                }
            }
@dacarley

This comment has been minimized.

Copy link

commented Mar 17, 2018

Also, for information sake, I've measured the overhead of the proxy at 797 gas, for a simple call:

            await proxy.acceptFreeTokens(testToken.address, owner, toWei(0.25, "ether"));
@GNSPS

This comment has been minimized.

Copy link
Owner Author

commented Mar 23, 2018

Thank you so much @dacarley! 🙌 Everything is corrected.
I'll have this in its own repo with tests soon enough, I hope. 😄

@dacarley

This comment has been minimized.

Copy link

commented Mar 26, 2018

Glad to help.

I'm planning on using this in my company's (BlitzPredict, www.blitzpredict.io) smart contracts shortly. I've extended the concept slightly, to allow creating multiple proxies simultaneously, thereby amortizing the fixed per-call overhead (approx 27401 gas) of calling the proxy factory.

By calling createManyProxies, 10 proxies at once costs 683,686 gas, while creating 100 proxies at once is 6,589,751 gas. Creating 100 proxies at once would likely take quite a while for the network to process, as it would consume essentially one entire block. Empirically, miners appear much more inclined to process transactions that cost < 1mil gas, and as such, you can have 10 transaction each of which create 10 proxies processed much more quickly than a single transaction that creates 100 proxies.

@dacarley

This comment has been minimized.

Copy link

commented Mar 27, 2018

Found another minor flaw -- the JUMPI target at opcode 54 should be 0x2F, not 0x30. I've attached my latest code below.

/***
* Shoutouts:
* 
* Bytecode origin https://www.reddit.com/r/ethereum/comments/6ic49q/any_assembly_programmers_willing_to_write_a/dj5ceuw/
* Modified version of Vitalik's https://www.reddit.com/r/ethereum/comments/6c1jui/delegatecall_forwarders_how_to_save_5098_on/
* Credits to Jorge Izquierdo (@izqui) for coming up with this design here: https://gist.github.com/izqui/7f904443e6d19c1ab52ec7f5ad46b3a8
* Credits to Stefan George (@Georgi87) for inspiration for many of the improvements from Gnosis Safe: https://github.com/gnosis/gnosis-safe-contracts
* 
* This version has many improvements over the original @izqui's library like using REVERT instead of THROWing on failed calls.
* It also implements the awesome design pattern for initializing code as seen in Gnosis Safe Factory: https://github.com/gnosis/gnosis-safe-contracts/blob/master/contracts/ProxyFactory.sol
* but unlike this last one it doesn't require that you waste storage on both the proxy and the proxied contracts (v. https://github.com/gnosis/gnosis-safe-contracts/blob/master/contracts/Proxy.sol#L8 & https://github.com/gnosis/gnosis-safe-contracts/blob/master/contracts/GnosisSafe.sol#L14)
* 
* 
* v0.0.2
* The proxy is now only 60 bytes long in total. Constructor included.
* No functionalities were added. The change was just to make the proxy leaner.
*
* Potential updates can be found at https://gist.github.com/GNSPS/ba7b88565c947cfd781d44cf469c2ddb
* 
***/

pragma solidity 0.4.19;

/* solhint-disable no-inline-assembly, indent, state-visibility, avoid-low-level-calls */

contract ProxyFactory {
    event ProxyDeployed(address proxyAddress, address targetAddress);
    event ProxiesDeployed(address[] proxyAddresses, address targetAddress);

    function createManyProxies(uint256 _count, address _target, bytes _data)
        public
    {
        address[] memory proxyAddresses = new address[](_count);

        for (uint256 i = 0; i < _count; ++i) {
            proxyAddresses[i] = createProxyImpl(_target, _data);
        }

        ProxiesDeployed(proxyAddresses, _target);
    }

    function createProxy(address _target, bytes _data)
        public
        returns (address proxyContract)
    {
        proxyContract = createProxyImpl(_target, _data);

        ProxyDeployed(proxyContract, _target);
    }
    
    function createProxyImpl(address _target, bytes _data)
        internal
        returns (address proxyContract)
    {
        assembly {
            let contractCode := mload(0x40) // Find empty storage location using "free memory pointer"
           
            mstore(add(contractCode, 0x0b), _target) // Add target address, with a 11 bytes [i.e. 23 - (32 - 20)] offset to later accomodate first part of the bytecode
            mstore(sub(contractCode, 0x09), 0x000000000000000000603160008181600b9039f3600080808080368092803773) // First part of the bytecode, shifted left by 9 bytes, overwrites left padding of target address
            mstore(add(contractCode, 0x2b), 0x5af43d828181803e808314602f57f35bfd000000000000000000000000000000) // Final part of bytecode, offset by 43 bytes

            proxyContract := create(0, contractCode, 60) // total length 60 bytes
            if iszero(extcodesize(proxyContract)) {
                revert(0, 0)
            }
           
            // check if the _data.length > 0 and if it is forward it to the newly created contract
            let dataLength := mload(_data) 
            if iszero(iszero(dataLength)) {
                if iszero(call(gas, proxyContract, 0, add(_data, 0x20), dataLength, 0, 0)) {
                    revert(0, 0)
                }
            }
        }
    }
}

/***
* 
* PROXY contract (bytecode)

603160008181600b9039f3600080808080368092803773f00df00df00df00df00df00df00df00df00df00d5af43d828181803e808314602f57f35bfd

* 
* PROXY contract (opcodes)

0 PUSH1 0x31
2 PUSH1 0x00
4 DUP2
5 DUP2
6 PUSH1 0x0b
8 SWAP1
9 CODECOPY
10 RETURN
11 PUSH1 0x00
13 DUP1
14 DUP1
15 DUP1
16 DUP1
17 CALLDATASIZE
18 DUP1
19 SWAP3
20 DUP1
21 CALLDATACOPY
22 PUSH20 0xf00df00df00df00df00df00df00df00df00df00d
43 GAS
44 DELEGATECALL
45 RETURNDATASIZE
46 DUP3
47 DUP2
48 DUP2
49 DUP1
50 RETURNDATACOPY
51 DUP1
52 DUP4
53 EQ
54 PUSH1 0x2f
56 JUMPI
57 RETURN
58 JUMPDEST
59 REVERT

* 
***/
@GNSPS

This comment has been minimized.

Copy link
Owner Author

commented Apr 4, 2018

Thank you so much @dacarley for taking the time! 😄 You rock!

@mohoff

This comment has been minimized.

Copy link

commented Jun 4, 2018

Hi, can I use this for current pragma solidity 0.4.24 too?
I tried it and creating the proxy works, but sending transactions to the newly created proxy result in Transaction execution error.

EDIT: downgraded everything to 0.4.19 but still running into this error. Playing around with truffle/web3 versions doesn't change it. Works well on Remix though.

@mo9527

This comment has been minimized.

Copy link

commented Jun 7, 2018

Can you upload the test code? please

@mohoff

This comment has been minimized.

Copy link

commented Jun 8, 2018

Yea, so my factory contract is as above. The proxy contract:

pragma solidity 0.4.19;

contract Master { 
  uint public test = 9999;

  function init()
      public
  {
      test = 12345;
  }

  function ()
      external
      payable
  {
      revert(); 
  }
}

The deployment and creation of the proxy contract goes well, but then calling init() fails. This is the method I'm running: master.methods.init().send({from: owner, gas: 7000000}) using web3@1.0.0beta34 out of the box. The created artifacts are from truffle@4.1.3 which comes with solc-js@0.4.19 (to match the pragma from this gist).

The result is:

{
  "message": "Method not found",
  "code": -32601
}

This is a corresponding tx: https://rinkeby.etherscan.io/tx/0x4684a6c116d36732687c25fca0f46281449c1d735417864281a8924a0c5b3c17

In case I remove the gas parameter from the send() method above, the output becomes:

Error: Transaction execution error.
Internal("Requires higher than upper limit of 80000000")

Some Github issues claim that's because estimateGas fails.

Any help appreciated :)

@ulcuber

This comment has been minimized.

Copy link

commented Jun 26, 2018

mstore(sub(contractCode, 0x09)
Seems like it writes 9 bytes of zeros before the free memory location. Is it safety?
I see it like this

/* Memory allocation
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++contractCode
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------------------------++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------------------------
----------------------------------------------------------------++++++++++++++++++++++000000000000000000000000f00df00df00df00df00df00df00df00df00df00d
----------------------------------------------000000000000000000603160008181600b9039f3600080808080368092803773
----------------------------------------------------------------++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------5af43d828181803e808314602f57f35bfd000000000000000000000000000000
 */
@GNSPS

This comment has been minimized.

Copy link
Owner Author

commented Jul 15, 2018

@ulcuber Definitely not safe! 😱

Bad design stemming from the proxy functions not being there in the first iteration! If you're sending a data bytes array that is padded to 32 bytes, last 9 bytes will be overwritten!

Maybe this is the problem you're facing @mohoff?

CC @dacarley for awareness

P.S.: Updating the code now.

@GNSPS

This comment has been minimized.

Copy link
Owner Author

commented Jul 24, 2018

To be clear, the unsafeness of this is just because this contract may be inherited and someone calling the function from within its child may get corrupted memory contents but now that I check closely neither is the free memory pointer being updated at the end so the problem still stands.

The point being that whoever was using this contract as was without inheriting it is totally fine and honestly I'm gonna revert the change since I feel that these functions should instead be considered external.

@rmeissner

This comment has been minimized.

Copy link

commented Aug 12, 2018

Was playing around with this for the GnosisSafe and used the following to generate the contractcode without the "unsafeness"

function buildProxyCode(address masterCopy)
        public
        pure
        returns (bytes contractCode)
    {
        contractCode = abi.encodePacked(
            bytes23(0x603160008181600b9039f3600080808080368092803773), 
            masterCopy, 
            bytes17(0x5af43d828181803e808314602f57f35bfd)
        ); 
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.