How to create a crypto exchange on the Ethereum blockchain using Node.js/React.js/Solidity - Part 5

in Project HOPE10 months ago

Hello, how are you doing? This write-up is post number four in a series of posts for the tutorial series on How to create a crypto exchange on the Ethereum blockchain using Node.js/React.js/Solidity.

I'll leave the link to previous posts in the series at the end of the post.

This series was inspired by a similar series by Dapp University.

The crypto exchange will contain a buy/sell feature and will have Ethereum and ERC20 token pairs.

After adding the tests for our token and exchange smart contract in the last post, in this post we are going to continue with developing the smart contract for the exchange.

Mainly the smart contract for the exchange will have a functionality that allows us to buy the ERC20 token using Ethereum and also to sell the token for Ethereum.

Before we start with the actual objective of this tutorial, there are some little functions that we need to include in our project which will be setting the stage for our actual objective.

If you check the tests we wrote in our last post, you will find that there are some values that were used in more than one place out of the array of tests we wrote.

What we want to do now is to declare those values in a public scope so that they can be called by any test or function in our test module.

In our Exchange.test.js file, right above the first describe() method used for testing, add the following code

let  token, exchange

before(async () => {
    token = await  Token.new()
    exchange = await  Exchange.new()
    await  token.transfer(exchange.address, '1000000000000000000000000')
})

In all the describe() methods in the file, remove any instance of the following declarations so that the only place they are declared is in the method I added above

token = await  Token.new()

exchange = await  Exchange.new()

await  token.transfer(exchange.address, '1000000000000000000000000')

After doing that, save your file and in the command line for the project, run the command

truffle test

You should get a result like the image below in your console.

enter image description here

Now that we have that out of the way, we can move forward to the next item on the menu.

Adding helper module to format module balances

On the line that says await token.transfer(exchange.address, '1000000000000000000000000'), '1000000000000000000000000' is equivalent to 1 million tokens even though the number of zeros after 1 is more than 6.

Let me explain why this is, if you check the smart contract code for the token in the file src/contracts/Token.sol, you will find a line that says

uint8  public decimals = 18;

The line above automatically configures the balance to be exactly 18 decimal points.

But the rule when it comes to Ethereum smart contracts is that decimal points are not expected to be explicitly declared, even though under the hood they will be considered.

So what we do in the case of Ethereum is that Ethereum balances are to be represented in their basic unit Wei, just like the Dollar value is can be represented in Cents and Bitcoin in Satoshi, this principle also applies to our token.

If 1 dollar can be represented by 100 cents, 1 of our token can be represented by 1000000000000000000. 1 million of our token can be represented by 1000000000000000000000000 which is the same the amount as what we have on the line await token.transfer(exchange.address, '1000000000000000000000000').

We can now say that 1 million of our token is in circulation presently.

What we want to do now is to create a helper function that helps us format the balances and make it more human readable.

So in the file Exchange.test.js, add the following code below the line require('chai').use(require('chai-as-promised')).should()

function  tokens(n) {
    return  web3.utils.toWei(n, 'ether');
}

Inside the before() method, replace the line await token.transfer(exchange.address, '1000000000000000000000000') with await token.transfer(exchange.address, tokens('1000000')).

Also, inside the test contract has token, replace the line assert.equal(balance.toString(), '1000000000000000000000000') with the following code

assert.equal(balance.toString(), tokens('1000000'))

Save the file again and run the command truffle test in the command line, if all goes well you should still get an image like the one below.

enter image description here

Exchange smart contract

I believe we are done with all the preliminary stuff and we can now start writing the smart contract for our exchange.

In our earlier tutorials, we created a file at the path src/contracts/Exchange.sol which will contain our exchange smart contract code.

The first thing we want to do in the Exchange.sol file is to import our token so that we can use it in the file.

Add the following code below the line pragma solidity ^0.5.0;

import  './Token.sol';

Now that we have imported the token smart contract we need to create a variable to represent the token, we do that by adding the following code in the smart contract object

Token public token;

Now, we need to create a new function that will help us identify where the token smart contract is located on the network.

To do that, we firstly need to update the migrations/2_deploy_contracts.js file in our project.

In 2_deploy_contracts.js, replace the line that says await deployer.deploy(Exchange); with the code

await  deployer.deploy(Exchange, token.address);

In the project command line run the following code

truffle migrate --reset

enter image description here

In Exchange.test.js, replace the line exchange = await Exchange.new() with the following code

exchange = await  Exchange.new(token.address)

Let's save the file and move on.

Buying Tokens

Now, we are going to create a function for buying the token using Ethereum.

What the function will do is to facilitate an exchange of funds between the buyer's wallet and the exchange wallet.

In order to make this happen, we will rely on the transfer() function in the token smart contract, we will have to call it in the Exchange.sol file and it will result in the requested amount of tokens being sent from the exchange wallet to the buyer's wallet.

In the Exchange.sol file, add the following code

function  buyTokens() public payable {
    // Calculate amount of tokens to buy
    uint tokenAmount = msg.value * rate;
    token.transfer(msg.sender, tokenAmount);
}

The contents of the function above calls the token.transfer() method in the token smart contract, remember that we initialized our token smart contract earlier storing it as a variable token.

The transfer() function takes two arguments, the first one msg.sender is the wallet address the token will be sent to, the second one tokenAmount is the variable that stores the amount of the token to be sent to the buyer.

Directly below the line that says Token public token;, add the following code

uint  public rate = 100;

Then save the file and in your command line, run the following command

truffle compile

If you get a result like in the image below, then we can move on and write a test module for our buy tokens feature.

enter image description here

Test module for buy tokens feature

In Exchange.test.js, add a new describe() method for the buyTokens() function.

Add the following code in the file below the other describe() functions

describe('buyTokens()', async () => {
    it('Allows user to instantly purchase tokens from the exchange', async () => {
        await  exchange.buyTokens({ from:  accounts[1], value:  web3.utils.toWei('1', 'ether') })
    })
})

If you run truffle test in your command line now, you should get something similar to this

enter image description here

We are not quite done yet, on the line that says contract('Exchange', (accounts), replace accounts with the following array,

[deployer, investor]

On the line that says await exchange.buyTokens({ from: accounts[1], replace accounts[1] with the variable investor and rerun truffle test in your command line.

If you get an identical result to the image we got in our last running of truffle test, it means everything is running correctly.

Also, we need to refactor our buyTokens() test code and add all the variables in a before() hook, just like we did to the previous tests.

Replace the entire describe() method for buyTokens() with the following code

describe('buyTokens()', async () => {
    let  result;
    
    before(async () => {
        result = await  exchange.buyTokens({ from:  investor, value:  web3.utils.toWei('1', 'ether') })
    })
 
    it('Allows user to instantly purchase tokens from the exchange', async () => {
        let  investorBalance = await  token.balanceOf(investor);
    assert.equal(investorBalance.toString(), tokens('100'));

    let  exchangeBalance;
    exchangeBalance = await  token.balanceOf(exchange.address);
    assert.equal(exchangeBalance.toString(), tokens('999900'))
    exchangeBalance = await  web3.eth.getBalance(exchange.address)
    assert.equal(exchangeBalance.toString(), web3.utils.toWei('1', 'Ether'))
    })
})

Save the file and run truffle test in the command line, if you get a response like in the image below, then you are on the right path.

enter image description here

Finally, in Exchange.sol, we wan to be able to emit an event whenever a token is purchased. The event emitted upon token buy can be used to create stuff like transaction histories and likes.

Under the line that says uint public rate = 100;, add the following code

event  TokenPurchased(
    address account,
    address token,
    uint amount,
    uint rate
);

Now, below the line that says token.transfer(msg.sender, tokenAmount);, add the following code

emit  TokenPurchased(msg.sender, address(token), tokenAmount, rate);

We want the test for buyTokens() to also inspect this event for every transaction and help us confirm whether the values being returned are correct.

In the Exchange.test.js file, below the line assert.equal(exchangeBalance.toString(), web3.utils.toWei('1', 'Ether')), add the following code

const  event = result.logs[0].args;

assert.equal(event.account, investor);

assert.equal(event.token, token.address)

assert.equal(event.amount.toString(), tokens('100').toString());

assert.equal(event.rate.toString(), '100')

Run truffle test and if you get a response like this image you are good to go

enter image description here

Lastly, we want to add a checker module that helps to check whether the exchange has enough tokens to be able to fill a buy order on the exchange, in a case where the exchange doesn't have enough tokens, the transaction has to fail and the funds returned to the buyer of the token.

So, in Exchnage.sol, above the line that says in buyTokens() function, add the following code

require(token.balanceOf(address(this)) >= tokenAmount);

Save the file and run the test again using truffle test in the command line, if it executes successfully like the other tests then, great we have successfully created a functionality for buying our tokens with Ether on the chain.

We have come to the end of this session for this series, we will continue with building the needed functionalities in the next post.

Links to previous posts in this series

Sort:  

@tipu curate

Congratulations @gotgame! You have completed the following achievement on the Hive blockchain and have been rewarded with new badge(s) :

You distributed more than 63000 upvotes. Your next target is to reach 64000 upvotes.

You can view your badges on your board And compare to others on the Ranking
If you no longer want to receive notifications, reply to this comment with the word STOP

Do not miss the last post from @hivebuzz:

Hive Power Up Day - Let's grow together!
Feedback from the Hive Meetup Vienna

Congratulations @gotgame! You received a personal badge!

Happy Hive Birthday! You are on the Hive blockchain for 3 years!

You can view your badges on your board And compare to others on the Ranking

Do not miss the last post from @hivebuzz:

Hive Power Up Day - The countdown is ticking
Hive Power Up Day - Let's grow together!