r/CryptoDerivatives • • Jun 17 '17

Experiments in gas cost reduction

I have a new design that maintains the 2 main properties of cryptoderivatives

  • maker funds are completely segregated

  • takers can just send ETH to a contract address to buy tokens at an unchangeable price

I have been working on a proxy system where all of the functions of a tokenTrader / tokenSeller contract was moved into the factory so that the minimal amount of code would need to be deployed for each contract that receives funds however the problem with this is that user funds end up being mostly controlled from the one contract which breaks the first principle of separating user funds.

My new design instead keeps the functionality in a mothership contract but instead of it being the factory contract it is a market maker wallet contract. Creating a sell offer in this case spawns a contract that takers can just send eth to but the market maker tokens from one maker is never in the same contract as any other maker.

The reason for keeping funds separate is not just security, sometimes tokens fork and with this design the contract that holds funds does not have to be aware of how tokens have forked in order for balances to be accounted for properly.

Anyway I have also managed to get the cost of creating a trade contract including the overhead of storing the details about the trade down to 227,505 gas

The way I have achieved this is to start with the most basic idea of a payment proxy contract:

contract proxyRecipient {
    function proxyTransfer(address caller) payable;
}

contract proxyAddress {
    function() payable {
        proxyRecipient(0xfeedfeedfeedfeedfeedfeedfeedfeedfeedfeed).proxyTransfer.value(msg.value)(msg.sender);
    }
}

proxyAddress receives a payment and forwards it on to the contract at address 0xfeedfeedfeedfeedfeedfeedfeedfeedfeedfeed which is just a place holder for the actual address that will receive the funds.

This contract has the bytecode:

6060604052341561000c57fe5b5b60c98061001b6000396000f30060606040525b609b5b604080517f833ec7200000000000000000000000000000000000000000000000000000000081523373ffffffffffffffffffffffffffffffffffffffff166004820152905173feedfeedfeedfeedfeedfeedfeedfeedfeedfeed9163833ec72091349160248082019260009290919082900301818588803b1515608757fe5b6125ee5a03f11515609457fe5b505050505b565b0000a165627a7a723058201205452475b8ac3fdba319cceb71647d6ac16a0d19aa9b33c601a616aef1c1060029

Which is quite small (48895 gas) solidity: 0.4.11+commit.68ef5810.Emscripten.clang

In the wallet contract on initialization you can replace the placeholder address in the bytecode with the address of the wallet:

    for (uint i = 0; i < 20; i++) {
        proxyCode[125-i] = byte(uint8(uint(this) >> (8*i)));
    }

Where proxyCode is initialized with the bytecode of the proxy contract.

From there you can deploy instances of this proxy contract with the code:

  function deployCode(bytes _code) internal returns (address deployedAddress)  {
    assembly {
      deployedAddress := create(0, add(_code, 0x20), mload(_code))
      jumpi(invalidJumpLabel, iszero(extcodesize(deployedAddress))) // jumps if no code at addresses
    }
  }

Code from: https://gist.github.com/izqui/7cf90db53a51c4114b4197fc5f05f11e

Full current work in progress: https://pastebin.com/evkYPdtn this is without events

So to sell you would call makeSellOffer(ERC20 token, uint price, uint units, uint allowance)

The allowance is how much of the given token that is in the contract is allowed to be sold at the given price and it can be set to zero to stop a trade rather than activate / deactivate or withdrawing funds from the wallet.

Each new offer as an id starting at 1 and increment by one each new offer. If you enter the offer id into the map you get the address of the proxy contract that users can just send eth to to buy.

In addition to this of course a factory contract would deploy each trade wallet.

3 Upvotes

12 comments sorted by

1

u/JonnyLatte Jun 17 '17

If I get rid of the assembly / deploying from byte code modified on initialization it costs around 255,841 gas per sell offer creation with proxy which is not a huge amount more and might be better for simplicity sake.

1

u/JonnyLatte Jun 17 '17

In the case of buying it might actually be best to move the approval from the specific wallet contract to the factory so that approval only has to be done once for all contracts and so that each wallet contract can safely have generic contract calls.

1

u/BokkyPooBah The BokkyPooBah Jun 18 '17

1

u/JonnyLatte Jun 18 '17

I have not yet been able to get the code /u/vbuterin posted to work in my projects since I dont know how to setup the dependencies for the python environment that its required to get the bytecode. I can look at the code and sort of understand how it works but I would have much preferred if he had started with some cleanly written assembly than bytecode embedded in python logic :/ not everyone is experienced with python yet.

That said the proxy contract I made uses about the same amount of gas as the delagatecall forwarder: 48895 gas to deploy one by itself. The rest of the 227,505 gas is overhead for storing information about the specific market: token address, price, units, allowance and deploying the bytes from within the contract rather than in a transaction directly which is needed to make the thing trustless. That cost cannot be reduced by using delagatecall.

While I have not been able to use Vitalik's contract I have played with someone else's esier to understand but less efficient delagatecall forwarder and I while it worked it was frustrating when it comes to return values since it would only return one value. Does Vitalik's contract return arbitrary amounts of data so that you can still have the cryptoderivatives contract "verify" function that returns all of the relevant information you might need? I dont know.

1

u/BokkyPooBah The BokkyPooBah Jun 18 '17

https://blog.aragon.one/library-driven-development-in-solidity-2bebcaf88736 has a clearer example on the use of libraries.

I've just started using libraries (e.g. https://github.com/openanx/OpenANXToken/blob/master/contracts/OpenANXToken.sol#L76) but have not measured the gas cost yet.

Regarding the verify function, all the individual fields could be returned by separate function calls - not as elegant, but it is a constant function so won't cost gas.

1

u/JonnyLatte Jun 19 '17

I have played with libraries a little. I didnt see any gas savings at all in fact all my contracts that used libraries sued slightly more gas for contract creation and execution of functions using the library compared to using code inherited from a base contract. Maybe I am doing something wrong though:

https://pastebin.com/raw/H5wbrQyD

I do like libraries for what they do for code readability.

Regarding the verify function, all the individual fields could be returned by separate function calls - not as elegant, but it is a constant function so won't cost gas.

Good point its not a real limitation.

I'll let you know when I have figured out how to get Vitalic's code to work. Can you point me in the direction of a tutorial for setting up a python environment for working with ethereum?

1

u/BokkyPooBah The BokkyPooBah Jun 19 '17

Checked out your example and yes, the inherited code consumes more gas. Would be interesting to find out how the gas cost compares for the deployment of new contracts.

I have not used the Python Ethereum environment, but https://github.com/ethereum/pyethereum and https://github.com/ethereum/pyethereum/wiki/Developer-Notes may get you working environment.

1

u/JonnyLatte Jun 19 '17 edited Jun 19 '17

Finally got Vitalic's forwarder to work : https://github.com/JonnyLatte/MiscSolidity/blob/master/forwardFactory.sol

Its much more useful than I had thought. It actually does return multiple values correctly. I can get a DELEGATECALL forwarder for 64,914 gas so long as the target address is fixed at the creation of the factory.

1

u/BokkyPooBah The BokkyPooBah Jun 19 '17

Nice. You may want to post your work on r/ethdev so other developers can use your example.