Interactions Between Contracts

Interactions between contracts

I have mentioned before the importance of viewing contracts as first class members of the ethereum network who have the same priveliges as external actors – this includes two very important abilities – to issue msg calls to other contracts, and to issue new contracts themselves.

In this tutorial we are going to build two contracts – one is a coinCaller which can interact with metaCoin contracts, the other will be mint which issues coins.

Firstly, lets remind ourselves of the metaCoin contract we have used previously:

contract metaCoin {	
	mapping (address => uint) public balances;
	function metaCoin() {
		balances[msg.sender] = 10000;
	}
	function sendToken(address receiver, uint amount) returns(bool successful){
		if (balances[msg.sender] < amount) return false;
		balances[msg.sender] -= amount;
		balances[receiver] += amount;
		return false;
	}
}

This is a very simple contract, one constructer function, one function that can be called by others.

Calling another contract

Any time one contract talks to another it uses the same ABI as we must use in order to call a function. So if we wish to build a contract that can call another contract our compiler must know the source code of this second contract in order that our contract knows how to access its functions.

To do this we first define the contract we wish to call and follow it with the contract we wish to create:

contract metaCoin {	
	mapping (address => uint) public balances;
	function metaCoin() {
		balances[msg.sender] = 10000;
	}
	function sendToken(address receiver, uint amount) returns(bool successful){
		if (balances[msg.sender] < amount) return false;
		balances[msg.sender] -= amount;
		balances[receiver] += amount;
		return false;
	}
}

contract coinCaller{
	//Our code goes here
}

If you were to copy and paste the above into alethzero you would see that the compiled code is for a contract called ‘coinCaller’ and that it has no functions. When passed a series of contracts the compiler assumes that the contract you wish to deploy to the blockchain is the final one – and that any other contracts it encounters are to be ignored until referenced in your main contract.

Let’s do that now. We are going to add code in our coinCaller that can call the sendToken function inside a metaCoin contract given the address:

contract metaCoin {	
	...
	}
}

contract coinCaller{
	function sendCoin(address coinContractAddress, address receiver, uint amount){
		metaCoin m = metaCoin(coinContractAddress);
		m.sendToken(receiver, amount);
	}
}

Some of this is new so I will go through it line by line:

1. Our function ‘sendCoin’ takes three arguments: the address of the metaCoin, the address of the recipient, and the number of coins we wish to send.
2. Here we define ‘m’ a metaCoin contract by explicity converting a given address to a metaCoin.
3. Now we can call the contract ‘m’s functions using the dot operator, and pass it the arguments recipient, and amount.

When you call another contracts function in this manner you are actually having the contract send the same data array that we used in our first tutorial. If the address you have passed it is not a valid metaCoin contract the message call will still be sent – but it wont necessarily execute any code.

Try deploying this contract in alethzero along with a metaCoin contract. You will need to first give the coinCaller’s address some coins as it will not have any coins (refer back to our first tutorial to see how that is done). If done correctly you should see the metaCoin contract updating just as it would if you called its functions directly.

Just like any other datatype, contracts can be held in storage and used as datatypes in mappings and structs. Take a look at the below code:

contract metaCoin {	
	...
	}
}

contract coinCaller{
	struct transfer{
		metaCoin coinContract;
		uint amount;
		address recipient;
	}
	mapping(uint => transfer) transfers;
	uint numTransfers;
	function sendCoin(address coinContractAddress, address receiver, uint amount){
		transfer t = transfers[numTransfers]; //Creates a reference t
		t.coinContract = metaCoin(coinContractAddress);
		t.amount = amount;
		t.recipient = receiver;
		t.coinContract.sendToken(receiver, amount);
		numTransfers++;
	}
}

In this example every one of our transfers is stored in coinCaller’s storage including the metaCoin contract that we sent the transfer to. Try deploying it along with a metaCoin contract again – this time you will see the transfer written to our coinCallers storage.

So far we have seen how to declare contract types and send message calls, however message calls are designed to return data to the sender, and our metaCoin contract has two functions which return data. The sendCoin function returns a boolean depending on whether there was sufficient balance to send the coins and the accessor function ‘balance’ returns the balance of the coins. Let’s rewrite our contract again so it can handle data returned from our metaCoin:

contract metaCoin {	
	...
	}
}

contract coinCaller{
	struct transfer{
		metaCoin coinContract;
		uint amount;
		address recipient;
		bool successful;
		uint balance;
	}
	mapping(uint => transfer) transfers;
	uint numTransfers;
	function sendCoin(address coinContractAddress, address receiver, uint amount){
		transfer t = transfers[numTransfers]; //Creates a reference t
		t.coinContract = metaCoin(coinContractAddress);
		t.amount = amount;
		t.recipient = receiver;
		t.successful = t.coinContract.sendToken(receiver, amount);
		t.balance = t.coinContract.balances(this);
		numTransfers++;
	}
}

Now when we send a message call to a metaCoin contract we store the return value of sendCoin which is a boolean value indicating that the transfer was successful. We have also added another message call – this time to the metaCoin’s ‘balances’ accessor function: t.balance = t.coinContract.balances(this); which is stored alongside other data on the transaction.

Creating a new contract

Contracts have the ability, like external actors, to create new contracts. In the previous section we showed how the solidity compiler needed to have access to the source code of any contract whose functions we wished to call. The same is true if we wish to give a contract the ability to create a new contract – the full source code needs to be known and is uploaded to the blockchain when we deploy it.

Lets create a new contract called ‘coinSpawner’ which produces metaCoin contracts:

contract metaCoin {	
	mapping (address => uint) public balances;
	function metaCoin() {
		balances[tx.origin] = 10000;
	}
	function sendToken(address receiver, uint amount) returns(bool successful){
		if (balances[msg.sender] < amount) return false;
		balances[msg.sender] -= amount;
		balances[receiver] += amount;
		return false;
	}
}

contract coinSpawn{
	function createCoin(){
		new metaCoin();
	}
}

Our createCoin function in coinSpawn creates a metaCoin contract when called using function ID 3d03ec29 – this function can be called multiple times and will produce multiple.

Note that we have made a subtle alteration to our metaCoin contract on line 4: balances[tx.origin] = 10000; we have replaced msg.sender with tx.origin so that the contract credits the sender of the original transaction (that’s us) *not* the sender of the message call (that’s the ‘coinSpawn’ contract).

Creating a new contract returns the address of the contract to the creator contract so lets setup our coinSpawn contract to handle this:

contract metaCoin {	
	mapping (address => uint) public balances;
	function metaCoin() {
		balances[tx.origin] = 10000;
	}
	function sendToken(address receiver, uint amount) returns(bool successful){
		if (balances[msg.sender] < amount) return false;
 		balances[msg.sender] -= amount;
 		balances[receiver] += amount;
 		return false;
 	}
} 
contract coinSpawn{
 	mapping(uint => metaCoin) deployedContracts;
	uint numContracts;
	function createCoin() returns(address a){
		deployedContracts[numContracts] = new metaCoin();
		numContracts++;
		return deployedContracts[numContracts];
	}
}

Finally, although the source code of the contract to be created must be set at compile time – it is possible for our coinSpawn contract to pass the constructor function arguments as long as it is setup to receive them:

contract metaCoin {	
	mapping (address => uint) public balances;
	function metaCoin(uint initialBalance) {
		balances[tx.origin] = initialBalance;
	}
	function sendToken(address receiver, uint amount) returns(bool successful){
		if (balances[msg.sender] < amount) return false;
 		balances[msg.sender] -= amount;
 		balances[receiver] += amount;
 		return false;
 	}
 } 

contract coinSpawn{
 	mapping(uint => metaCoin) deployedContracts;
	uint numContracts;
	function createCoin(uint initialBalance) returns(address a){
		deployedContracts[numContracts] = new metaCoin(initialBalance);
		numContracts++;
		return deployedContracts[numContracts];
	}
}

Our coinSpawn contract can now Deploy metaCoin contracts for its users at will, with users specifying the starting balance. Deploy the above out in Alethzero and send it a transaction like the one below to Deploy a metaCoin:

$0x80f7884d
100000

If you’ve got the hang of the above why not try and build a contract that can both deploy metaCoin contracts and interact with them (hint: make sure you take care to notice the differences between tx.origin and msg.sender).

Leave a comment