Search code examples
blockchainethereumsolidity

Questions about implementing ERC20 tokenswap contract


I've tried myself to implement token swap contract from https://solidity-by-example.org/app/erc20/. And I have some questions about it.

  1. Is it impossible to declare & assign variable inside constructor function? I've tried but, it didn't work.
  2. Can I use ERC20 instance instead of IERC20 instance? and what's the advantage of using IERC20 instance?
contract TokenSwap {
    IERC20 public token1;
    address public owner1;
    uint public amount1;
    IERC20 public token2;
    address public owner2;
    uint public amount2;

    constructor(
        address _token1,
        address _owner1,
        uint _amount1,
        address _token2,
        address _owner2,
        uint _amount2
    ) {
        token1 = IERC20(_token1);
        owner1 = _owner1;
        amount1 = _amount1;
        token2 = IERC20(_token2);
        owner2 = _owner2;
        amount2 = _amount2;
    }
  1. In example it made new private function _safeTransferFrom instead of using just transferFrom function inside the swap function. What is the advantage of this?
    function swap() public {
        require(msg.sender == owner1 || msg.sender == owner2, "Not authorized");
        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");
    }

Solution

  • 1

    You can declare and assign variables inside the constructor, they will just be local and only accessible inside the constructor after they have been declared. Please provide the exact attempt you made so I can see what was wrong.

    2

    A contract's interface provides all the information needed to interact with that contract. Specifically, what is needed is the function signatures of the contracts external/public functions. This is used to encode the calldata that is sent to the contract in order to call a specific function, and is completely obtainable from the contract's interface. See https://docs.soliditylang.org/en/v0.8.11/abi-spec.html for more details.

    3

    I am not too sure about the purpose of this new function. transferFrom and its subsequent _transfer call (see source code at https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol) contain require statements that check the validity of the transfer, i.e. whether the spenders allowance is large enough and whether the actual token owner (the one's whose tokens are being spent) has the required tokens. Unless I have missed something, these checks determine whether a transaction is valid or not. As such, either the overall transaction will revert, due to one of these require statements having a false condition, or it will suceed. This makes the additional require(sent, "Token transfer failed") redundant, as the only time it will be called is when the transfer is a sucess.

    Note: This method could be useful if you are using the address type's call or delegatecall methods to call the function. This is because execeptions in this subcalls do not bubble up to the calling contract, so you could use the returned boolean to check if transfer was successful. See Error handling: Assert, Require, Revert and Exceptions section of https://docs.soliditylang.org/en/v0.8.11/control-structures.html for more detail on this.