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 .
- 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 .
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
}
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
}
Make sure the 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 address.
/**
* Receives payments from
*/
function sendPayment() external payable
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 variable. The escrow then initiates the transfer of funds.
/**
* Withdraw funds to
*/
function withdraw() external onlyOwner
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 . 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 .
/**
* Checks balance available to withdraw
* @return the balance
*/
function balance() external view onlyOwner returns (uint256)
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 ;
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 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()
We are calling sendPayment from accounts[1], because as you recall, accounts[0] creates the contract and will be set to our payment gateway .
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() will fail.
Finally let’s withdraw our funds to the :
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
Published at Sat, 20 Apr 2019 08:37:56 +0000