NicolaBernini / BlockchainAnalysis

Analysis of Blockchain related Stuff
3 stars 2 forks source link

A Simple TokenSwap Example in Remix #15

Open NicolaBernini opened 3 years ago

NicolaBernini commented 3 years ago

Overview

This is a simple example in Solidity that can be run in Remix to understand how an ERC20 Token Swap managed by a Swapping Smart Contract works

1. Smart Contracts

1.1 ERC20 Token

An ERC20 Token can be simply created by using the OpenZeppelin implementation that can be easily imported in Remix as follows

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.1.0/contracts/token/ERC20/ERC20.sol";

contract Token1 is ERC20 {

    constructor () public ERC20("Token", "TKN1") {
        _mint(msg.sender, 1000000 * (10 ** uint256(decimals())));
    }
}
Role Address
Wallet1 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
Token1 0xD7ACd2a9FD159E69Bb102A1ca21C9a3e3A5F771B
Wallet2 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
Token2 0xAc40c9C8dADE7B9CF37aEBb49Ab49485eBD3510d

1.2 Swap Smart Contract

The Swapping Contract will have a swap() method that swaps an amount X1 of TKN1 from Wallet1 to Wallet2 and at the same time an amount X2 of TKN2 from Wallet2 to Wallet1

Since it needs to manipulate the 2 ERC20 Tokens, it needs to import the definition of the IERC20 interface that can be again found in the OpenZepllin framework

Its implementation can be simply

// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/token/ERC20/IERC20.sol";

contract TokenSwap {
    IERC20 public token1;
    address public owner1;
    uint256 public amount1;
    IERC20 public token2;
    address public owner2;
    uint2356 public amount2;

    constructor(
        address _token1,
        address _owner1,
        uint256 _amount1,
        address _token2,
        address _owner2,
        uint256 _amount2
    ) {
        token1 = IERC20(_token1);
        owner1 = _owner1;
        amount1 = _amount1;
        token2 = IERC20(_token2);
        owner2 = _owner2;
        amount2 = _amount2;
    }

    function swap() public {
        require(msg.sender == owner1 || msg.sender == owner2, "Only one of the two token owners is allowed");
        require(
            token1.allowance(owner1, address(this)) >= amount1,
            "Token 1 allowance too low"
        );
        require(
            token2.allowance(owner2, address(this)) >= amount2,
            "Token 2 allowance too low"
        );

        _safeTransferFrom(token1, owner1, owner2, amount1);
        _safeTransferFrom(token2, owner2, owner1, amount2);
    }

    function _safeTransferFrom(
        IERC20 token,
        address sender,
        address recipient,
        uint amount
    ) private {
        bool sent = token.transferFrom(sender, recipient, amount);
        require(sent, "Token transfer failed");
    }
}

The swap() public method essentially does 2 things

The actual implementation of the swaps is in the private function _safeTransferFrom() (that is not accessible outside of the SC so it can be reached only after the require() have been passed) and it is wrapping the ERC20 transferFrom() with a require() that checks its return value

This is a common pattern: use require() to check conditions as soon as possible so for example

Let's proceed to compile and deploy also this contract to the Swap address passing the arguments about Wallet1, Token1, Wallet2 and Token2

At the end of this step the situation will be something like

Role Address
Wallet1 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
Token1 0xD7ACd2a9FD159E69Bb102A1ca21C9a3e3A5F771B
Wallet2 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
Token2 0xAc40c9C8dADE7B9CF37aEBb49Ab49485eBD3510d
Swap 0x438eacEBf3F2a1c3E8560277345E83ff228355bE

2. Run

To run the swap() then 2 steps are required

2.1 Setting the allowance

To be able to swap a given amount of token, each Token Onwer needs to allow the expense in advance

This is done by calling from the owner address the ERC20 approve() method passing both spender address and the amount

In the swap case the spender is the Swap Contract while the other owner is the recipient, so in both cases we need to set spender=0x438eacEBf3F2a1c3E8560277345E83ff228355bE so if we want to swap 100 of Token1 for 50 of Token2 then

image

and this can be checked by calling the ERC20 allowance() method from Wallet1 to Swap

image

image

and then we check

image

2.2 Swap

3. Check

image

image

So that confirms the swap has been successful