Calling Future Contracts from Contracts in Ethereum - Dev Log 6

Quick Updates:

  1. I’m going to plug the Blockchain Show one more time as I was on last week and it is a great way to get an intro into some of the highlevel concepts of Catallax.
  2. There was an awesome turnout at the Houston Bitcoin Meetup this week.  I think it was the biggest one yet.  I guess the attendance tracks the price.  Topics of discussion include "What are you working on?" A: Catallax on Ethereum" to "How are you learning?" A: Lots of stack exchange and writing crappy code.

Code:

On to the code for this week and this one is a doozy.  

After building my FunkyCoin wallet last week I realized a pretty big flaw.  Having a wallet is kind of useless if you need to transfer your coin and/or ETH back to a real address before you can interact with a contract.  For example, our FunkyCoin wallet couldn’t buy an ENS domain name because it doesn’t know the code signature of ENS.  We could include the code interface in our Wallet contract, but if wanted to include every service our code would get huge.  And worse, what if someone builds an amazing service next week that runs on FunkyCoin?  Everyone with an existing wallet would have to deploy a new wallet with the new code signatures and transfer their FunkyCoin to it.
 
I may be missing something major so please reach out to me if I’m bungled something up. This seems like a pretty big use case flaw in ethereum. So...I went looking for a solution.  And...I think I found one.
 
Warning:  From what I understand what I’m about to do appears to be recommended against by most who have written even a little bit about this topic.  Use at your own risk.
 
Goal:  Write a contract that I can own that can call any other contract that may come online in the future.
 
It turns out there is a way to call code and contracts that you don’t really know about from a contract.  This is accomplished by using the CALL feature of solidity.  The best documentation I could find on it is here under the members section, but that seemed to be a bit incomplete.
 
If you have an address A in your solidity contract and A is a contract, you can call one of its functions by doing:
 
    A.call(the_contract_signature_signature, [optional: a, set, of, parameters])
 
The stuff that isn’t documented very well that found were the following:
 
The param the_contract_signature_signature looks like “bytes4(sha3(“MyFunction(address,uint256)”))”  where the param items are pretty specific.  I didn’t explore the whole scope of value types, but I did find out that uint doesn’t work...you need uintXXX where XXX is the byte length.  Also, if your function has no arguments it needs to be “MyFunction()”.  What you end up with here is a 4 byte signature that points to the function in the contract.  Apparently, to save space the full function name isn’t used and they use the sha3 to guarantee uniqueness.  Only taking the first 4 bytes probably could lead to some collisions, but maybe not.  There is probably a function that calculates this.  Have fun chasing down that bug if it ever happens.
 
The [a, set, of, parameters] is just a list of params where each one is the hex representation of byte32 padded on the left.  So an address of 0xa845e57fcc55024711da2652b6956e9f72a252fe becomes 0x000000000000000000000000a845e57fcc55024711da2652b6956e9f72a252fe and the number 11 becomes 0x000000000000000000000000000000000000000000000000000000000000000b.  I did not test strings and I’m not sure what you would need to do if your string goes over 32 bytes.  Maybe this is disallowed in ethereum?
 
Using this function we are going to create an escape hatch function into our contract that allows us to call arbitrary code in other contracts as if we were the contract.  I think that this would be an alternate solution to the ERC20 bug and ERC223 solution where people are sending coins to contracts and they get stuck.  A contract doesn’t have a primary key so you can't just construct a function call for it because you can’t sign the transaction as if you are the contract.  If you call the contract and then it calls something I think the pk is baked in so things work.
 
The one drawback is that the CALL function doesn’t return the return value of the function you call.  It returns true or false.  False is thrown if the other contract throws, so you always need to check this value and throw if you get false.
 
HELP NEEDED:  If you know of a way to inspect the blockchain to determine what the actual return value of this call was, please reach out to me and explain.  I don’t necessarily need this value in real time, but since the blockchain is deterministic it seems like we should be able to find it once the transaction block is committed to the chain.
 
In theory, this setup should work.  So let’s take a look at the function in our BeepBopBot that will be our digital selves on the blockchain:

Gist here

Load in Remix Here
 
 

contract BeepBopBot {

    address public owner;
    bytes32 emptyBytes;

    event Impersonation( address indexed caller, address indexed executer, string functionSig);

    function BeepBopBot(){
      owner = msg.sender;
    }

    function impersonate(address aContract, string functionSig, 
        bytes32 _1,
        bytes32 _2,
        bytes32 _3,
        bytes32 _4,
        bytes32 _5,
        bytes32 _6,
        bytes32 _7,
        bytes32 _8) returns (bool ok){

        bool result = false;
        bytes4 sig = bytes4(sha3(functionSig));

        if(_8 != emptyBytes){
            result = aContract.call(sig, _1, _2, _3, _4, _5, _6, _7, _8);
        } 
        else if(_7 != emptyBytes){
            result = aContract.call(sig, _1, _2, _3, _4, _5, _6, _7);
        }
        else if(_6 != emptyBytes){
            result = aContract.call(sig, _1, _2, _3, _4, _5, _6);
        }
        else if(_5 != emptyBytes){
            result = aContract.call(sig, _1, _2, _3, _4, _5);
        }
        else if(_4 != emptyBytes){
            result = aContract.call(sig, _1, _2, _3, _4);
        }
        else if(_3 != emptyBytes){
            result = aContract.call(sig, _1, _2, _3);
        }
        else if(_2 != emptyBytes){
            result = aContract.call(sig, _1, _2);
        }
        else if(_1 != emptyBytes){
            result = aContract.call(sig, _1);
        }
        else {
            result = aContract.call(sig);
        }
        if(result == false) throw;

        address theSender = msg.sender;
        Impersonation(theSender, this, functionSig);
        return result;

    }
}


So a couple things you should notice off the bat:

  • This is really ugly and there is probably a better way to this.
  • You probably want to use an owner only throw at the top of this so that only the owner can use this escape hatch.
  • You probably also want to make sure that the contract isn't callin it self.  If you called Impersonate on your own contract you'd eventually run out of gas.
  • We are limited to 8 parameters so if a future contract has more than 8 we won’t be able to call it with this contract.
  • This contact doesn’t do much except impersonate, but it could be added to pretty much any contract as an escape hatch.
  • I have no idea about the gas costs of this and I may just be burning all kinds of gas.
  • You will see that we are broadcasting an Impersonation event that I’m hoping I can hook to and find function returns after the transaction is committed to the blockchain.

 
Now that we have something that should in theory work, how can we test it?  To do this I’ve created a simple Name registration service that lets you claim addresses as yours.  When you claim an address a new NameObject contract is created and you are set as the owner.  You can then trade around the NameObject to others.  It doesn’t do much but help us prove our point.
 
A real world application would be creating a multisig wallet that is able to bid on an ENS name, claim it, hold it, and interact with it.
 
Here is our simple service:
 

contract NameService {

    mapping(address => address) public addressContracts;
    uint public coolNumber;

    event NameReserved(address indexed caller, address indexed nameObject);


    function NameService(){
        coolNumber = 12;
    }

    function lookUp(address theAddress) constant returns(address contractAddress){
        return addressContracts[theAddress];
    }

    function Claim(address theAddress) returns (address aName){
        if(addressContracts[theAddress] != 0) throw;
        address anAddress = theAddress;
        aName = new NameObject(msg.sender, anAddress);
        addressContracts[theAddress] = aName;
        address theSender = msg.sender;


        NameReserved(theSender, aName);
        return aName;
    }

    function ClaimSet(address theAddress, uint256 aCoolNumber) returns (address aName){
        //note the uint256, uint didn't work

        if(addressContracts[theAddress] != 0) throw;
        coolNumber = aCoolNumber;

        return Claim(theAddress);
    }

    function FindCoolNumber() returns (uint256 aNumber){

        aNumber = coolNumber;
        return aNumber;
    }


}

contract NameObject{

    address public owner;
    address public claimedAddress;
    bool public hereIam;

    event IExist(address owner);
    event Transfered(address owner);



    function NameObject(address aOwner, address aClaimedAddress){
        owner = aOwner;
        claimedAddress = aClaimedAddress;
    }

    function ProveExistance() returns (bool ok){
        hereIam = true;
        IExist(owner);
        return true;
    }

    function Transfer(address __newOwner) returns(bool ok){
        if(msg.sender != owner) throw;
        owner = __newOwner;
        Transfered(owner);
        return true;
    }
}

I have actually deployed and tested this on the Ropsten contract.  Here are the steps that I followed and the transaction numbers:

Create our BeepBopBot:

Txn: https://ropsten.etherscan.io/tx/0x1d441c4196ebf049e52f893002ac267035c7bece43cedd8be4bc3b1784a384fc

Resulting contract: https://ropsten.etherscan.io/address/0x38a31dbad2448286b3687363e72bb21f96ccbe56

Create our NameService:

Txn: https://ropsten.etherscan.io/tx/0xc1685751f4ad3879ff18be828d404c55183790184b4a6a2738ed812efbd1818b


Resulting contract: https://ropsten.etherscan.io/address/0x71c253362a44bed4b85ac21d1274dd4f7fd9d516

Try to find the Cool Number from the NameService as BeepBopBot.  It should be 12:

 Call:  BeepBopBot.impersonate("0x71c253362a44bed4b85ac21d1274dd4f7fd9d516","FindCoolNumber()")

Txn: https://ropsten.etherscan.io/tx/0x4a49418a8999b73818fdf0ca2f9226934f6869bd50a43042bec9f032c4178113
 
Not much happens in this call except that an Impersonation event is generated.  The actual cool number is lost to the ether(pun intended).
 
Help Needed:  I can assume that when BeepBopBot called FindCoolNumber that 12 was returned because that is what it was set to when the contract was created.  Is there a way to verify that?  Replay it?  What tools would I use?  How would I isolate it?

Try to claim and address as BeepBopBot:

 Call: BeepBopBot.impersonate("0x71c253362a44bed4b85ac21d1274dd4f7fd9d516","Claim(address)","0x000000000000000000000000a845e57fcc55024711da2652b6956e9f72a252fe"

Txn: https://ropsten.etherscan.io/tx/0x7a350203f4d0ad2f1d5af4515213b71f7af114573ac9a55eeae8ddda712e8521

Contract Created: https://ropsten.etherscan.io/address/0xf8A7141Dd1341b013808b4f583C32ACb3345DebA
 

If I look at the transaction on etherscan it shows me that an internal transaction created a new contract at 0xf8A7141Dd1341b013808b4f583C32ACb3345DebA. I’m hoping that this is my new name object and that BeepBopBot is the owner.
 
We can check the value of our cool number by using https://www.myetherwallet.com and clicking on the contracts tab.  Switch over to the ropsten network using the network selector in the top right.  Enter our 0x71c253362a44bed4b85ac21d1274dd4f7fd9d516 address for our deployed Name service and copy the ABI from the solidity contract in remix / browser solidity.  We can see that our cool number is now nine!  We didn’t put much security on this thing so anyone can change the number by claiming and address with ClaimSet.
 

Try to claim an address and set the cool number as BeepBopBot:

We are going to claim another address, this one is just one off of the last(ff instead of fe on the first param)
 
Call: BeepBopBot.impersonate("0x71c253362a44bed4b85ac21d1274dd4f7fd9d516","ClaimSet(address,uint256)","0x000000000000000000000000a845e57fcc55024711da2652b6956e9f72a252ff","0x0000000000000000000000000000000000000000000000000000000000000009")

Txn: https://ropsten.etherscan.io/tx/0xf5d3a25647798d1b974d21ce6981b1f612db87f609b953375e7a02eda0f5ecb2

Contract Created: https://ropsten.etherscan.io/address/0x7169576471f3067e51bafb2bfe0816e4c6580cdc

Checking in MEW:

 

Make sure that FindCoolNumber now returns 9:

 Txn: https://ropsten.etherscan.io/tx/0x0c058b8be54b5f847b7feb41e5d883bb8defb11ed6bdbee8c3445c1903778e1b
 

Prove Existence of my NameObject

This should set a bool in one of my new NameObject contracts.  I’d like to actually see this data, but for now I’ll assume it is working correctly.
 
Call: BeepBopBot.impersonate("0xf8A7141Dd1341b013808b4f583C32ACb3345DebA","ProveExistance()")

Txn: https://ropsten.etherscan.io/tx/0x940f8e464a12123411b4d86165df65eec165c5e16c646bd2d2269de70d5c26a3
 

Try to transfer my NameObject to a different Owner:

Call: BeepBopBot.impersonate(“0xf8A7141Dd1341b013808b4f583C32ACb3345DebA","Transfer(address)","0x000000000000000000000000a845e57fcc55024711da2652b6956e9f72a252f9")

Txn: https://ropsten.etherscan.io/tx/0x71104e8e69ee023799599224fb3491c906f51294c9a79615dec0f5d10baff04d
 
Try it again and it should fail because we are no longer the owners
 
Call: BeepBopBot.impersonate(“0xf8A7141Dd1341b013808b4f583C32ACb3345DebA","Transfer(address)","0x000000000000000000000000a845e57fcc55024711da2652b6956e9f72a252f9")

Txn: https://ropsten.etherscan.io/tx/0xb4e20d75033bc5e9c7fff3bdfe30d376b802995c7557e7a3a751b24ab93b318c

If you inspect the above transaction you will see that it threw as we were expecting.
 
It looks like our system works!
 
In summary here are the things left to solve:

  • Can we deduce the return value of impersonated functions from the blockchain?  If not directly can we replay them and pull the value out?  How?  This is important if some of these functions are returning created hashes or other important data for the contract.
  • If we make the impersonate function payable, does sent ETH flow through the contracts properly?  This would be important if you were sending ETH to an ICO auction in the name of a multisig wallet.
  • Is there a better signature for the impersonate function?  Right now if one of your params is 0 none of the other params after it will be sent.  This is probably an easy fix of adding the param number to the impersonate function, but there are probably other ways of doing this and probably much more efficient assembly level code that could do the same thing.

 If you have any answers to these questions please reach out to me at @hypercatallax on twitter or on our r/Catallax reddit.  You can also take a crack at these stack exchange question:
 
https://ethereum.stackexchange.com/questions/17368/can-i-get-the-return-value-of-a-call-from-a-contract-from-the-blockchain
 
 If this is interesting to you and you'd like to see where we are going with Catallax, please pick up my book Immortality.

Donations always accepted at:

BTC: 1AAfkhg1NEQwGmwW36dwDZjSAvNLtKECas

ETH and Tokens: 0x148311c647ec8a584d896c04f6492b5d9cb3a9b0

If you would like more code articles like this please consider becoming a patron on patreon.