Transfers and approval of ERC-20 tokens from a solidity smart contract

In the previous tutorial we studied the anatomy of an ERC-20 token in Solidity on the Ethereum blockchain. In this article we’ll see how we can use a smart contract to interact with a token using the Solidity language.

For this smart contract, we’ll create a really dummy decentralized exchange where a user can trade Ethereum with our newly deployed ERC-20 token.

For this tutorial we’ll use the code we wrote in the previous tutorial as a base. Our DEX will instantiate an instance of the contract in it’s constructor and perfom the operations of:

  • exchanging tokens to Ether
  • exchanging Ether to tokens

We’ll start our Decentralized exchange code by adding our simple ERC20 codebase:

1pragma solidity ^0.6.0;
2
3interface IERC20 {
4
5 function totalSupply() external view returns (uint256);
6 function balanceOf(address account) external view returns (uint256);
7 function allowance(address owner, address spender) external view returns (uint256);
8
9 function transfer(address recipient, uint256 amount) external returns (bool);
10 function approve(address spender, uint256 amount) external returns (bool);
11 function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
12
13
14 event Transfer(address indexed from, address indexed to, uint256 value);
15 event Approval(address indexed owner, address indexed spender, uint256 value);
16}
17
18
19contract ERC20Basic is IERC20 {
20
21 string public constant name = "ERC20Basic";
22 string public constant symbol = "ERC";
23 uint8 public constant decimals = 18;
24
25
26 event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
27 event Transfer(address indexed from, address indexed to, uint tokens);
28
29
30 mapping(address => uint256) balances;
31
32 mapping(address => mapping (address => uint256)) allowed;
33
34 uint256 totalSupply_ = 100 ether;
35
36 using SafeMath for uint256;
37
38 constructor(uint256 total) public {
39 balances[msg.sender] = totalSupply_;
40 }
41
42 function totalSupply() public override view returns (uint256) {
43 return totalSupply_;
44 }
45
46 function balanceOf(address tokenOwner) public override view returns (uint256) {
47 return balances[tokenOwner];
48 }
49
50 function transfer(address receiver, uint256 numTokens) public override returns (bool) {
51 require(numTokens <= balances[msg.sender]);
52 balances[msg.sender] = balances[msg.sender].sub(numTokens);
53 balances[receiver] = balances[receiver].add(numTokens);
54 emit Transfer(msg.sender, receiver, numTokens);
55 return true;
56 }
57
58 function approve(address delegate, uint256 numTokens) public override returns (bool) {
59 allowed[msg.sender][delegate] = numTokens;
60 emit Approval(msg.sender, delegate, numTokens);
61 return true;
62 }
63
64 function allowance(address owner, address delegate) public override view returns (uint) {
65 return allowed[owner][delegate];
66 }
67
68 function transferFrom(address owner, address buyer, uint256 numTokens) public override returns (bool) {
69 require(numTokens <= balances[owner]);
70 require(numTokens <= allowed[owner][msg.sender]);
71
72 balances[owner] = balances[owner].sub(numTokens);
73 allowed[owner][msg.sender] = allowed[owner][msg.sender].sub(numTokens);
74 balances[buyer] = balances[buyer].add(numTokens);
75 emit Transfer(owner, buyer, numTokens);
76 return true;
77 }
78}
79
80library SafeMath {
81 function sub(uint256 a, uint256 b) internal pure returns (uint256) {
82 assert(b <= a);
83 return a - b;
84 }
85
86 function add(uint256 a, uint256 b) internal pure returns (uint256) {
87 uint256 c = a + b;
88 assert(c >= a);
89 return c;
90 }
91}
92
📋 Copy

Our new DEX smart contract will deploy the ERC-20 and get all the supplied:

1contract DEX {
2
3 IERC20 public token;
4
5 event Bought(uint256 amount);
6 event Sold(uint256 amount);
7
8 constructor() public {
9 token = new ERC20Basic();
10 }
11
12 function buy() payable public {
13 // TODO
14 }
15
16 function sell(uint256 amount) public {
17 // TODO
18 }
19
20}
21
📋 Copy

So we know have our DEX and it has all the token reserve available. The contract has two functions:

  • buy: The user can send Ether and get tokens in exchange
  • sell: The user can decide to send tokens to get ether back

The buy function

Let’s code the buy function. We’ll first need to check the amount of Ether the message contains and verify that the contracts own enough tokens and that the message has some Ether in it. If the contract owns enough tokens it’ll send the number of tokens to the user and emit the Bought event.

Note that if we call the require function in the case of an error the Ether sent will directly be reverted and given back to the user.

To keep things simple, we just exchange 1 token for 1 Ether.

1function buy() payable public {
2 uint256 amountTobuy = msg.value;
3 uint256 dexBalance = token.balanceOf(address(this));
4 require(amountTobuy > 0, "You need to send some Ether");
5 require(amountTobuy <= dexBalance, "Not enough tokens in the reserve");
6 token.transfer(msg.sender, amountTobuy);
7 emit Bought(amountTobuy);
8}
9
📋 Copy

In the case where the buy is successful we should see two events in the transaction: The token Transfer and the Bought event.

Two events in the transaction: Transfer and Bought

The sell function

The function responsible for the sell will first require the user to have approved the amount by calling the approve function beforehand. Then when the sell function is called, we’ll check if the transfer from the caller address to the contract address was succesful and then send the Ethers back to the caller address.

1function sell(uint256 amount) public {
2 require(amount > 0, "You need to sell at least some tokens");
3 uint256 allowance = token.allowance(msg.sender, address(this));
4 require(allowance >= amount, "Check the token allowance");
5 token.transferFrom(msg.sender, address(this), amount);
6 msg.sender.transfer(amount);
7 emit Sold(amount);
8}
9
📋 Copy

If everything works you should see 2 events (a Transfer and Sold) in the transaction and your token balance and Ethereum balance updated.

Two events in the transaction: Transfer and Sold

From this tutorial we saw how to check the balance and allowance of an ERC-20 token and also how to call Transfer and TransferFrom of an ERC20 smart contract using the interface.

Once you make a transaction we have a Javascript tutorial to wait and get details about the transactions that were made to your contract and a tutorial to decode events generated by token transfers or any other events as long as you have the ABI.

Here is the complete code for the tutorial:

1pragma solidity ^0.6.0;
2
3interface IERC20 {
4
5 function totalSupply() external view returns (uint256);
6 function balanceOf(address account) external view returns (uint256);
7 function allowance(address owner, address spender) external view returns (uint256);
8
9 function transfer(address recipient, uint256 amount) external returns (bool);
10 function approve(address spender, uint256 amount) external returns (bool);
11 function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
12
13
14 event Transfer(address indexed from, address indexed to, uint256 value);
15 event Approval(address indexed owner, address indexed spender, uint256 value);
16}
17
18
19contract ERC20Basic is IERC20 {
20
21 string public constant name = "ERC20Basic";
22 string public constant symbol = "ERC";
23 uint8 public constant decimals = 18;
24
25
26 event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
27 event Transfer(address indexed from, address indexed to, uint tokens);
28
29
30 mapping(address => uint256) balances;
31
32 mapping(address => mapping (address => uint256)) allowed;
33
34 uint256 totalSupply_ = 10 ether;
35
36 using SafeMath for uint256;
37
38 constructor() public {
39 balances[msg.sender] = totalSupply_;
40 }
41
42 function totalSupply() public override view returns (uint256) {
43 return totalSupply_;
44 }
45
46 function balanceOf(address tokenOwner) public override view returns (uint256) {
47 return balances[tokenOwner];
48 }
49
50 function transfer(address receiver, uint256 numTokens) public override returns (bool) {
51 require(numTokens <= balances[msg.sender]);
52 balances[msg.sender] = balances[msg.sender].sub(numTokens);
53 balances[receiver] = balances[receiver].add(numTokens);
54 emit Transfer(msg.sender, receiver, numTokens);
55 return true;
56 }
57
58 function approve(address delegate, uint256 numTokens) public override returns (bool) {
59 allowed[msg.sender][delegate] = numTokens;
60 emit Approval(msg.sender, delegate, numTokens);
61 return true;
62 }
63
64 function allowance(address owner, address delegate) public override view returns (uint) {
65 return allowed[owner][delegate];
66 }
67
68 function transferFrom(address owner, address buyer, uint256 numTokens) public override returns (bool) {
69 require(numTokens <= balances[owner]);
70 require(numTokens <= allowed[owner][msg.sender]);
71
72 balances[owner] = balances[owner].sub(numTokens);
73 allowed[owner][msg.sender] = allowed[owner][msg.sender].sub(numTokens);
74 balances[buyer] = balances[buyer].add(numTokens);
75 emit Transfer(owner, buyer, numTokens);
76 return true;
77 }
78}
79
80library SafeMath {
81 function sub(uint256 a, uint256 b) internal pure returns (uint256) {
82 assert(b <= a);
83 return a - b;
84 }
85
86 function add(uint256 a, uint256 b) internal pure returns (uint256) {
87 uint256 c = a + b;
88 assert(c >= a);
89 return c;
90 }
91}
92
93contract DEX {
94
95 event Bought(uint256 amount);
96 event Sold(uint256 amount);
97
98
99 IERC20 public token;
100
101 constructor() public {
102 token = new ERC20Basic();
103 }
104
105 function buy() payable public {
106 uint256 amountTobuy = msg.value;
107 uint256 dexBalance = token.balanceOf(address(this));
108 require(amountTobuy > 0, "You need to send some Ether");
109 require(amountTobuy <= dexBalance, "Not enough tokens in the reserve");
110 token.transfer(msg.sender, amountTobuy);
111 emit Bought(amountTobuy);
112 }
113
114 function sell(uint256 amount) public {
115 require(amount > 0, "You need to sell at least some tokens");
116 uint256 allowance = token.allowance(msg.sender, address(this));
117 require(allowance >= amount, "Check the token allowance");
118 token.transferFrom(msg.sender, address(this), amount);
119 msg.sender.transfer(amount);
120 emit Sold(amount);
121 }
122
123}
124
📋 Copy
Sam Richards
Last edit: @samajammin, September 25, 2020
See contributors
Edit page