5

I am working on an XC20 implementation of an existing ERC20. I am basing the contract off of the Astar here.

I noticed there is some code inherited from the ERC20 contract via the ERC20Wrapper contract that is not needed for this implementation. The ERC20Wrapper is usually used to create a wrapped version of an existing token, but in the case with XC20, the wrapped token already exists and just needs to be bridged to the underlying token. I am curious why the example still has this unused code.

I implemented it with removing all of the extra ERC20 code and was able to get a successful run, being able to mint/burn my xcToken by depositing/withdrawing my underlying token.

Here is my contract code for reference.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import {IERC20} from './Dependencies/IERC20.sol';
import {BAIToken} from './BAIToken.sol';
import {Ownable} from "./Dependencies/Ownable.sol";


// This xBai interface was recieved from the astar XCM documentation 
// (https://github.com/AstarNetwork/astar-frame/blob/674356e7b611e561aaf9bf581452cab965cf8e87/examples/assets-erc20/XcBurrito.sol#L12)
interface IERC20Plus is IERC20 {
    function mint(address beneficiary, uint256 amount) external returns (bool);
    function burn(address who, uint256 amount) external returns (bool);
}


contract XBaiWrapper is Ownable{

    IERC20 public immutable underlyingToken;
    IERC20Plus public crossChainToken;

    constructor(IERC20 _underlyingToken, IERC20Plus _crossChainToken) {
        underlyingToken = _underlyingToken;
        crossChainToken = _crossChainToken;
    }

    // --- Events --- 

    event xBaiMinted(address to, uint256 amount);
    event xBaiBurned(address from, uint256 amount);
    event baiSupplyChanged(uint256 newTotalSupply);

    // --- Admin functions ---

    function mintXBai(address _to, uint256 _amount) public onlyOwner {

        require( // Make sure amount to mint is greater than 0
            _amount != 0, 
            "Mint amount must be greater than 0"
        );

        _mintCrossChainToken(_to, _amount);
        emit xBaiMinted(_to, _amount);
    }

    function burnXBai(address _from, uint256 _amount) public onlyOwner {

        require( // Make sure amount to burn is greater than 0
            _amount != 0, 
            "Burn amount must be greater than 0"
        );

        _burnCrossChainToken(_from, _amount);
        emit xBaiBurned(_from, _amount);
    }


    //=====================================================================
    // This functiontion is to be used if the total supply of xBai is 
    // greater than the amount of Bai in this contract. The caller should
    // supply this contract with Bai so that the total supply of xBai is
    // equal to this contract's balance of Bai. 
    //=====================================================================
    function supplyBai(address _from, uint256 _amount) public onlyOwner {

        require( // Make sure amount to recover is greater than 0
            _amount != 0, 
            "Burn amount must be greater than 0"
        );

        underlyingToken.transferFrom(_from, address(this), _amount);

        uint256 newBaiSupply = underlyingToken.balanceOf(address(this));
        emit baiSupplyChanged(newBaiSupply);
    }  

    //=====================================================================
    // This function is to be used if the total supply of xBai is less than
    // the amount of Bai in this contract. The call should recover enough 
    // Bai so this contract's balance of Bai is equal to the total supply
    // of xBai. 
    //=====================================================================
    function recoverBai(address _to, uint256 _amount) public onlyOwner {
        
        require( // Make sure amount to recover is greater than 0
            _amount != 0, 
            "Burn amount must be greater than 0"
        );

        underlyingToken.transfer(_to, _amount);

        uint256 newBaiSupply = underlyingToken.balanceOf(address(this));
        emit baiSupplyChanged(newBaiSupply);
    }

    // --- Public functions ---

    //=====================================================================
    // Allow a user to deposit underlying tokens and mint the corresponding 
    // number of wrapped tokens.
    //=====================================================================
    function depositFor(address _account, uint256 _amount) public returns (bool) {

        require( // Make sure amount to deposit is greater than 0
            _amount != 0, 
            "Deposit amount must be greater than 0"
        );

        underlyingToken.transferFrom(msg.sender, address(this), _amount);
        _mintCrossChainToken(_account, _amount);

        return true;
    }

    //=====================================================================
    // Allow a user to burn a number of wrapped tokens and withdraw the 
    // corresponding number of underlying tokens.
    //=====================================================================
    function withdrawTo(address _account, uint256 _amount) public returns (bool) {

        require( // Make sure amount to withdraw is greater than 0
            _amount != 0, 
            "Withdrawal amount must be greater than 0"
        );

        _burnCrossChainToken(msg.sender, _amount);
        underlyingToken.transfer(_account, _amount);

        return true;
    }

    // --- Internal functions ---

    function _mintCrossChainToken(address _to, uint256 _amount) internal {

        // add here your pre-mint hooks hooks if needed
        
        require(
            crossChainToken.mint(_to, _amount), 
            "Minting xc token failed"
        );

        // add here your post-mint hooks hooks if needed
        emit xBaiMinted(_to, _amount);
    }

    function _burnCrossChainToken(address _from, uint256 _amount) internal {

        // add here your pre-burn hooks hooks if needed

        require(
            crossChainToken.burn(_from, _amount), 
            "Burning xc token failed"
        );

        // add here your post-burn hooks hooks if needed
        emit xBaiBurned(_from, _amount);
    }

}

1 Answer 1

3

The BAI is existing ERC20 token on Astar EVM. In order to make that token xChain it needs to be connected via Astar precompile ERC20Plus with the new xcBAI asset. For this connection to happen we need some kind of contract to wrap the connection between evm token and xChain asset. I believe you understood this part and made this wrapper contract.

You are right in saying that ERC20Wrapper.sol by OpenZeppelin is not mandatory for this operation. It is used in the example since existing UIs are familiar with ERC20Wrapper interface while your new wrapper contract will require custom implementation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.