This article explores the technique of getting one Smart Contract to call another Smart Contract and is a follow-up to the Escrow Service article that I wrote in May 2018. The Safe Remote Purchase Smart Contract is, in my opinion, the clearest example to explain Smart Contracts to anyone who needs to understand what it does. Safe Remote Purchase has several quirky characteristics. In this article, I will explain one of them.
The Safe Remote Purchase Smart Contract can only be used once and is a hassle to monitor especially if a frequent seller has several of these contracts running at any one time. A frequent seller will need something to:
- spawn new "Safe Remote Purchase" contracts on every new sale.
- tell him where the "Safe Remote Purchase" contracts that he has initiated are.
- allow him to monitor each of them, their state, and who the buyers are.
This article will do point 1 and 2. Point 3 can be done by pasting the ABI of "Safe Remote Purchase" contract in an Ethereum Wallet like Mist. I could build a DApp to do this too! But this article will focus on just point 1 and 2.
The Business Logic
Peter decides to start a business selling widgets. He executes StartEscrow.
John will like to buy a widget for 2ETH so Peter executes the newPurchase() function of StartEscrow. This creates a new Purchase Contract. Peter can see the address of this contract, and paste this address into a BlockExplorer to monitor its progress. He passes the address of this newly instantiated Purchase Contract address to John who can then interact with like as documented here.
Sally comes by and wishes to buy a widget for 4ETH. Peter executes newPurchase() function again. This creates a brand new Purchase Contract.
At the end of the Purchase Contract transaction, Peter receives ETH from the buyers.
Execution
Here's how StartEscrow will be executed in the Remix IDE.
Deploy StartEscrow by copying the StartEscrow.sol codes into Remix. Hit [Deploy] to deploy this Smart Contract.
Assuming that Peter wishes to sell a widget for 2ETH, enter 2ETH and click [newPurchase]. This will create a new Purchase contract.
Now, assume that Peter wishes to sell another widget for 4ETH, enter 4ETH and click [newPurchase]. This will create one more new Purchase contract.
To find out how many contracts were created so far, click [getContractCount].
To find out the contract address of, say, the first contract, enter 0 in "seePurchase" and click [call].Copy the address of the Purchase contract.
We will now play the role of the buyer, John. John opens his own Remix IDE and enter the Purchase contract address and click [At Address]. He clicks [seller] to find out Peter's address (Note: This is not the contract's address, but Peter's address).
Next he enters 2 ETH and clicks [confirmPurchase] to confirm that he is buying this widget.
Note that this Purchase contract now holds 4ETH; 2 from John and 2 from Peter.
John confirms that he has received the widgets from Peter by clicking [confirmReceived].
Check Etherscan. 1 ETH has been transferred to John, and 2 ETH to Peter.
Changes to Purchase Contract
I made minor changes to the original Safe Remote Purchase contract. Here's where I changed:
constructor(address contractSeller) public payable {
seller = contractSeller;
value = msg.value / 2;
require((2 * value) == msg.value);
}
I added a parameter "address contractSeller" to the constructor so that the purchase contract knows the address of the person to send ETH to after the Purchase process has completed. I decided that it will be the contract owner who will be transferred ETH as payment rather than having the contract holds the ETH.
StartEscrow
Here's how StartEscrow creates a new Purchase Contract:
// deploy a new purchase contract
function newPurchase()
public
payable
returns(address newContract)
{
Purchase c = (new Purchase).value(msg.value)(address(msg.sender));
contracts.push(c);
lastContractAddress = address(c);
return c;
}
newPurchase is payable, so you must provide ETH to call it - specifically, ETH values in even numbers because that's what Safe Remote Purchase requires. It instantiates a new Purchase contract, passing in the value of the ETH that newPurchase receive as well as the address of msg.sender (which is the person who executed the StartEscrow contract). It pushes the Purchase contract that it created into a contracts array and saves the contract's address in the variable lastContractAddress.
So every time you run the newPurchase() function by providing some ETH to it, it starts a brand new Purchase contract.
Here's how to pull out the address of a Purchase contract that you started:
//tell me a position and I will tell you its address
function seePurchase(uint pos)
public
constant
returns(address contractAddress)
{
return address(contracts[pos]);
}
Just provide the position of the Purchase contract in the array, and seePurchase will tell you its address. I wrote this so that I can iterate through a list of Purchase Contract addresses to display its current state in a user interface which I promise to develop later as a follow up to this article as long as I do not get distracted. :)
You can find both Purchase and StartEscrow in a single StartEscrow.sol file here.
Next Steps
I have made a mental note to develop a DApp around this StartEscrow contract so that a user can start new Purchase contracts and monitor those that he has started. Perhaps I will do this soon!
This article was written based on the code sample for executing one smart contract from another smart contract by Rob Hitchens on StackExchange.
Photo by chuttersnap on Unsplash