Using the OpenZeppelin Escrow Library

Will Shahda
5 min readApr 20, 2019

--

Buried in the OpenZeppelin documentation is brief mention of their escrow library. Turns out this library is useful for a variety of payment applications. Even situations where it is not necessary to hold funds for a period of time, the escrow library can protect against security risks by implementing the withdrawal pattern.

Basically, if you call transfer inside another function, an attacker could create a contract that causes the function to fail and potentially wreak havoc on your contract. If you call it in a separate withdrawal function, the attacker cannot abuse any other part of your contract.

In this tutorial, we are going to use the escrow library to implement a simple payment gateway.

Our payment gateway will have a few basic features:

  • It should create an OpenZeppelin escrow contract.
  • It should accept payments and send them to the escrow contract.
  • It should allow the owner of the gateway to withdraw funds from the escrow to a wallet.
  • It should allow the owner of the gateway to view the balance they can withdraw.

If you need help setting up a project before we start coding, read my article Build Smart Contracts with OpenZeppelin and Truffle.

Create the payment gateway contract

This is just an empty contract, nothing surprising here.

pragma solidity ^0.5.2;/**
* @title Payment gateway using OpenZeppelin Escrow
* @author Will Shahda
*/
contract PaymentGateway {}

Make the payment gateway ownable

There will be a couple of functions we will want to restrict access to. The easiest way to do this is make our contract “ownable”. Whoever creates an ownable contract becomes its owner, and will be able to call the restricted functions.

Let’s extend our contract to make it “ownable”.

import 'openzeppelin-solidity/contracts/ownership/Ownable.sol';/**
* @title Payment gateway using OpenZeppelin Escrow
* @author Will Shahda
*/
contract PaymentGateway is Ownable {}

Create an escrow

The escrow library is meant to be used alongside other contracts. Instead of extending Escrow, we create an instance of it for our payment gateway.

import 'openzeppelin-solidity/contracts/ownership/Ownable.sol';
import 'openzeppelin-solidity/contracts/payment/escrow/Escrow.sol';
contract PaymentGateway is Ownable {
Escrow escrow;
constructor() public {
escrow = new Escrow();
}
}

Add a wallet

We need an address to receive the funds collected from payments.

Let’s name the variable wallet and pass the address into the contract’s constructor. We do this instead of using msg.sender in case the contract creator wants to use a separate address for receiving funds.

contract PaymentGateway is Ownable {
Escrow escrow;
address payable wallet;
/**
* @param _wallet receives funds from the payment gateway
*/
constructor(address payable _wallet) public {
escrow = new Escrow();
wallet = _wallet;
}
}

Make sure the wallet variable is also payable.

Create function for sending payments

This is the first function that makes use of the escrow contract. Payers will use it to make payments destined for the wallet address.

/**
* Receives payments from customers
*/
function sendPayment() external payable {
escrow.deposit.value(msg.value)(wallet);
}

Notice we are calling the deposit function with the value of ether sent to our sendPayment function. If we try to use the transfer function on our escrow, the payment would not be accounted for, and the owner would not be able to withdraw it.

Create function for withdrawing funds

All this function has to do is call the escrow’s withdraw function with our wallet variable. The escrow then initiates the transfer of funds.

/**
* Withdraw funds to wallet
*/
function withdraw() external onlyOwner {
escrow.withdraw(wallet);
}

This function is the reason we made our contract “ownable”. We don’t want just anyone to initiate a withdrawal, even if funds can only be sent to our wallet. Because our contract extends Ownable, all we had to do was add the onlyOwner modifier.

Create function for viewing balance

This is another function we want to restrict to the owner.

To get the balance, we make a call to the escrow that returns all the deposits stored for our wallet.

/**
* Checks balance available to withdraw
* @return the balance
*/
function balance() external view onlyOwner returns (uint256) {
return escrow.depositsOf(wallet);
}

As you can see, we are returning a uint256. It is good practice to use the SafeMath library for uint256 because it throws errors on overflow instead of silently causing problems. Let’s do that as well.

import 'openzeppelin-solidity/contracts/math/SafeMath.sol';contract PaymentGateway is Ownable {
using SafeMath for uint256;
...

Deploy the payment gateway

If we are using Truffle, we need to write a migration file to deploy our contract. In the migration, we must remember to specify a wallet for the constructor of the payment gateway.

const PaymentGateway = artifacts.require(‘PaymentGateway’);module.exports = function(deployer, network, accounts) {
deployer.deploy(PaymentGateway, accounts[0]);
};

Because accounts[0] is used to create contract instances, it will become our payment gateway’s owner, and is therefore a good choice for a wallet address.

Test the payment gateway

Let’s make sure our contract works. After deployment, open the Truffle console and run the following commands:

const accounts = await web3.eth.getAccounts()
const instance = await PaymentGateway.deployed()
instance.sendPayment({ from: accounts[1], value: web3.utils.toWei(‘1’, ‘ether’) })

We are calling sendPayment from accounts[1], because as you recall, accounts[0] creates the contract and will be set to our payment gateway wallet.

Now let’s check the balance available for us to withdraw:

instance.balance()

This should output a balance of 1 ether. Keep in mind the balance will be displayed in wei as a hex value.

Because only the owner can view the balance, calling instance.balance({ from: accounts[1] }) will fail.

Finally let’s withdraw our funds to the wallet:

instance.withdraw()

Again, only accounts[0] can call this function because it is the owner.

After withdrawal, we can check the balance of accounts[0] to see that is has been incremented by ~1 ether (subtracting the ether spent on gas for transactions).

Real world applications

Any dApp that accepts payment could benefit from an escrow. It could be something simple like the payment gateway we just created. It could be something complex like a two sided marketplace with third party arbitration. No matter what the application, you can protect yourself from security issues using the withdrawal pattern implemented by OpenZeppelin’s escrow library.

View the code for this tutorial

https://github.com/opz/EscrowTutorial

--

--