The ethereum blockchain has a powerful ability to programmatically transfer assets using smart contracts. In this post we will take a look at creating our own escrow contract and running it to transfer funds between accounts via a third party.

Setting up solc

Before we can start work on our contract we first need to setup the compiler, solc. There are several methods for installing solc that can be found here https://solidity.readthedocs.io/en/latest/installing-solidity.html, my personal favourite for keeping my environment clean is to use the official docker image. To do this I create the following alias:

$ alias solc="docker run --rm -v `realpath .`:/solidity ethereum/solc:stable"

This mounts your local directory into the containers working directory and so the solc can be used as normal. For example:

$ solc -o target --bin --abi contract.sol

This will compile the bin and abi files for your contract (defined in contract.sol) and saves them to the target directory.

Our first contract

Now we have setup the solidity compiler we can create our first contract.

As our first example lets create a simple escrow contract where a trusted third party (the arbitrator) can validate sending an amount of ETH from a sender to a receiver taking a small fee. The arbitrator will control the creation, cancellation and transferal of funds and the sender is responsible for adding funds to the contract.

To create the escrow contract we will need to create a contract that knows the sender, the receiver and the arbitrator as well as the amount to send and the arbitrators cut:

pragma solidity ^0.4.23;

contract Escrow {
    address sender;
    address receiver;
    address arbitrator;

    uint value;
    uint arbitratorCut;

    constructor(address _sender, address _receiver, uint _value) public {
        sender = _sender;
        receiver = _receiver;
        arbitrator = msg.sender;

        value = _value;
        arbitratorCut = value / 100; // give 1% to the arbitrator
    }
}

There are a few important features to point out, the first line tells the compiler which version of the solidity spec to use. Next we have the contract definition starting with the variable definitions. addresses are used to records the addresses for the transaction and uints are used to store values. Next we construct the contract taking parameters for the sender, receiver and value. We take the arbitrator from the address creating the contract (msg.sender).

While this is enough to create a contract it's a little dull. Currently the contract has no funds or methods for adding funds. Let's fix that:

contract Escrow {
    // ...
    // variable definitions and constructor
    // ...

    modifier onlySender() {
        require(
            msg.sender == sender,
            "Only the sender can call this."
        );
        _;  // replaced by the function body at compile time
    }

    function deposit() public onlySender payable {}
}

The function adding the funds is deposit even though it's content is empty. There are 2 important modifiers specified, the first is onlySender defined above, this ensures that only the sender can add funds to the contract, if the address adding the funds does not match the sender then the function will exit without adding the funds. The second is the built-in modifier payable which takes a transaction and adds the funds to the contract.

Now we need to add methods for the arbitrator to interact with:

contract Escrow {
    // ...
    // variable definitions and constructor
    // ...

    // ...
    // sender depositing functions
    // ...

    modifier onlyArbitrator() {
        require(
            msg.sender == arbitrator,
            "Only the arbitrator can call this."
        );
        _;
    }

    modifier walletIsFull() {
        require(
            (value + arbitratorCut) <= address(this).balance,
            "There is not enough cash in the wallet"
        );
        _;
    }

    function cancel() public onlyArbitrator {
        selfdestruct(sender);
    }

    function send() public onlyArbitrator walletIsFull {
        // send the value to the receiver
        receiver.transfer(value);

        // send the arbitrators cut
        arbitrator.transfer(arbitratorCut);

        // destroy the contract and return any excess to the sender
        selfdestruct(sender);
    }
}

Here we have added 2 functions and 2 new modifiers. The first modifier (onlyArbitrator) acts in a very similar way to onlySender but checks the transaction sender is the arbitrator not the sender. The second modifier (walletIsFull) checks that the contract has enough funds to cover the transaction and arbitrator fees.

The first function cancel can only be performed by the arbitrator and calls the built-in function selfdestruct. This destroys the contract on the blockchain and returns any remaining funds to the address in the first argument (in this case the sender).

The second function send ensures everyone gets their share. Firstly it can only be fired by the arbitrator and secondly it can only be performed if the wallet has enough funds to cover everybody's share. If these conditions are met it will send the correct amount to the receiver, the arbitrators cut to the arbitrator and finally destroys the contract sending any excess back to the sender.

Building and wrapping in js

To interact with the network we will be using geth, a post on setting up geth and a temporary test network here https://wildfish.com/blog/2018/05/23/creating-a-local-ethereum-testing-network/.

To load the contract in geth we must first build it and wrap the result in javascript. To build the contract (assuming it is saved in escrow.sol) run:

$ solc -o target --bin --abi escrow.sol

This will create a new directory, target containing Escrow.abi and Escrow.bin, to make this content available to geth we need to wrap it in a javascript function to create a contract, this will look something like this (saved to escrow.js):

var factory = eth.contract(<content of target/Escrow.abi>);
var compiled = "0x<content of the target/Escrow.bin>"

function createEscrow(_sender, _receiver, _arbitrator, _value) {
    return factory.new(_sender,_receiver, _value, {from: _argitrator, data: compiled, gas: 4000000}, function(e, contract){
        if(e) {
            console.error(e);
            return;
        }

        if(!contract.address) {
            console.log("Contract transaction send: TransactionHash: " + contract.transactionHash + " waiting to be mined...");

        }
        else {
            console.log("Contract mined! Address: " + contract.address);
        }
    });
}

Assuming we have multiple accounts setup on your network we can then create our first contract from a geth interactive console we call:

> var arbitrator = accounts[1]
> var sender = accounts[2]
> var receiver = accounts[3]

> loadScript('escrow.js')

> personal.unlockAccount(arbitrator)
Unlock account <arbitrator account hash>
Passphrase: <arbitrator passphrase>
true

> [eth.getBalance(arbitrator), eth.getBalance(sender), eth.getBalance(receiver)]
[720000180000000000000, 720000180000000000000, 0]

> escrow = createEscrow(sender, receiver, arbitrator, 1000)

> [eth.getBalance(arbitrator), eth.getBalance(sender), eth.getBalance(receiver)]
[719991242154000000000, 720000180000000000000, 0]

escrow is an instance of our contract that we can interact with. If we want to load this instance again we must record the contracts address and use:

> escrow2 = eth.contract(<abi file contents>).at(<original contract address>)

Once we have an instance of the contract we can then interact with it using the functions we defined in the solidity file, to add funds:

> personal.unlockAccount(sender)
Unlock account <sender account hash>
Passphrase: <sender passphrase>
true

> escrow.deposit.sendTransaction({from: sender, value: 2000})
"<transaction hash>"

> [eth.getBalance(arbitrator), eth.getBalance(sender), eth.getBalance(receiver)]
[719991242154000000000, 719999792873999998000, 0]

> eth.getBalance(escrow.address)
2000

Now we have enough funds in the escrow the arbitrator is free to transfer the funds:

> personal.unlockAccount(arbitrator)
Unlock account <arbitrator account hash>
Passphrase: <arbitrator passphrase>
true

> escrow.send.sendTransaction({from: arbitrator})
"<transaction hash>"

> [eth.getBalance(arbitrator), eth.getBalance(sender), eth.getBalance(receiver)]
[719990580042000000010, 719999792873999998990, 1000]

Now the value has been sent to the receiver, the arbitrators cut has been paid to the arbitrator and the excess has been paid back to the sender.

comments powered by Disqus

Pingbacks

Pingbacks are open.