Writing a Token Market Contract

February 27, 2018 by Todd Proebsting

[EDIT 2018-03-13] This post has been updated to use Solidity 0.4.21 event syntax.

This post will demonstrate how to write a smart contract that creates a token marketplace, where people can buy and sell ERC20 tokens. The smart contract acts like
eBay
by enabling sellers to list tokens for sale, and then brokering sales to buyers. This post relies on concepts introduced in our post on
ERC20 tokens.

ERC20 token owners may want to sell some of their tokens. Of course, it would be natural to sell the tokens for ether and to conduct that sale via a smart contract. To help buyers find sellers, a single marketplace contract allows sellers to list tokens for sale—in a given quantity and for a given price—and then brokers sales when approached by buyers.

My marketplace contract will support three essential transactions:

Sellers can list a token for sale. The listing will include the quantity available and the price/unit.

Buyers can buy a token advertised in a given listing. The purchase quantity can be any amount up to the total available in the listing, and the price will be computed based on the listing’s price/unit.

Sellers can cancel an existing listing. Cancellation doesn’t affect any previously made sales, but it will prevent any subsequent sales.

(More) Floating Point Woes

Solidity’s lack of support for floating point numbers presents challenges when pricing tokens. It’s natural to give prices in wei per token unit, but sometimes integer values are insufficient. For instance, it is impossible to express 1.5 wei/unit or 0.0001 wei/unit with a simple integer value.

In this contract, I’m going to use a very simple technique—the contract will use a rational number expressed as numerator/denominator, where both numerator and denominator are 256-bit unsigned integers. This will give plenty of precision to express any reasonable price.

Solidity structs

Each listing is composed of five related values: the seller’s address, the token’s address, the quantity available (in units), and the wei/unit price given as a rational number.

Solidity’s structs are very similar to C’s and Go’s as a means for treating related data as a unit.

Solidity Arrays

The marketplace contract must keep track of all the sellers’ listings, and it will do so in a dynamically-sized array. Solidity arrays are indexed from 0:

Listing[]listings;

Once a listing is added to the array, it will be referenced by its location (index) in the listings array. Future purchase and cancellation transactions will refer to the listing’s index.

Listing Tokens for Sale

Adding a listing to the marketplace contract is very straightforward: the contract will create a new listing struct with the appropriate values and append it to the end of the listings array.

In addition to storing the listing, the contract will log an event to announce the listing change to the outside world. The event will include the seller’s address and the listing’s index. The seller’s address is indexed to help the seller determine the indices of its listing(s).

Listing memory x declares a Listing struct that will reside in memory, which is temporary, rather than having it reside in persistent storage.

Listing(...) creates a new Listing struct with named fields set to the argument values.

listings.push(x) adds an element to the end of the listing array.

This contract will use the ERC20 token approve/transferFrom pattern for delegating token transfers. So, the buyer must approve the marketplace contract to transfer the listed tokens prior to any buyers attempting to buy those tokens. The marketplace contract never checks that the appropriate approve has happened—it simply assumes it has.

Buying Tokens

Making a purchase is also straightforward: the buyer indicates the index of the listing to be used, and the quantity to purchase. The buyer must also attach the appropriate amount of ether to the transaction:

The code checks that the tokens requested are available, updates the amount available, and transfers the tokens to the buyer.

The code computes the total cost of the transaction, checks that the buyer attached that amount of ether, and transfers the ether to the seller.

The code logs an event, which can alert the seller or a DApp to the purchase.

The statement, Listing storage listing = listings[index], exploits the fact that storage variables are references to persistent storage. This means that any changes to the fields of listing will actually be to the underlying listings[index] struct.

Note that while the marketplace contract never explicitly checked that the seller had approved the transfer, the buy transaction will fail if the transferFrom does not succeed.

Cancelling a Listing

A seller may cancel a listing at any time. Cancellation will simply delete—zero out—the fields of the listing struct.