Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TASK] Design a cross-chain asset registry for evm restaked assets #814

Closed
1xstj opened this issue Nov 11, 2024 · 4 comments · Fixed by #820
Closed

[TASK] Design a cross-chain asset registry for evm restaked assets #814

1xstj opened this issue Nov 11, 2024 · 4 comments · Fixed by #820
Assignees

Comments

@1xstj
Copy link
Contributor

1xstj commented Nov 11, 2024

The goal is to design and implement a crosschain asset-registration logic so that we can restake from other evm chains (and message is then passed to tangle).

@github-project-automation github-project-automation bot moved this to Not Started 🕧 in Webb Universe Nov 11, 2024
@1xstj
Copy link
Contributor Author

1xstj commented Nov 12, 2024

Cross chain asset restaking

Challenge : How to mint/represent foreign chain restaked tokens on tangle

Synthetic Asset = foreign restaked token
Foreign = restaked on a non-tangle evm chain

1. Using Asset Precompile to Mint Synthetic Assets

For every restaked token, a contract factory will exist on tangle evm, the tangle evm contract will first check if an asset exists to represent the “foreign asset”, if it does then use that, else call the assets precompile to create an asset. In both cases, the next step is to call the assets precompile and then mint the synthetic assets.

Example of token factory :

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

// Interface for the Assets Precompile
interface IAssetsPrecompile {
    function exists(address foreignAsset) external view returns (bool);
    function createAsset(address foreignAsset, string memory name, string memory symbol, uint8 decimals) external returns (address);
    function mint(address asset, address to, uint256 amount) external;
}

contract TokenFactory {
    address public assetsPrecompile; // Address of the Assets Precompile contract

    // Mapping to store foreign asset -> synthetic asset
    mapping(address => address) public foreignToSynthetic;

    // Event logs
    event AssetCreated(address indexed foreignAsset, address indexed syntheticAsset);
    event SyntheticMinted(address indexed syntheticAsset, address indexed to, uint256 amount);

    // Constructor to initialize the assets precompile address
    constructor(address _assetsPrecompile) {
        assetsPrecompile = _assetsPrecompile;
    }

    /**
     * @dev Checks if a synthetic asset exists for the given foreign asset.
     * If not, it creates one using the assets precompile.
     * Then mints synthetic tokens for the specified recipient.
     *
     * @param foreignAsset Address of the foreign asset.
     * @param to Address to mint the synthetic tokens to.
     * @param amount Amount of synthetic tokens to mint.
     */
    function handleRestake(address foreignAsset, address to, uint256 amount) external {
        address syntheticAsset = foreignToSynthetic[foreignAsset];

        // Check if the synthetic asset already exists
        if (syntheticAsset == address(0)) {
            // Call the assets precompile to check if the asset exists
            bool exists = IAssetsPrecompile(assetsPrecompile).exists(foreignAsset);

            if (!exists) {
                // If the asset does not exist, create a new synthetic asset
                string memory name = string(abi.encodePacked("Synthetic ", toString(foreignAsset)));
                string memory symbol = string(abi.encodePacked("s", shortAddress(foreignAsset)));
                uint8 decimals = 18; // Default decimals, can be adjusted

                syntheticAsset = IAssetsPrecompile(assetsPrecompile).createAsset(foreignAsset, name, symbol, decimals);
                foreignToSynthetic[foreignAsset] = syntheticAsset;

                emit AssetCreated(foreignAsset, syntheticAsset);
            }
        }

        // Mint the synthetic tokens
        IAssetsPrecompile(assetsPrecompile).mint(syntheticAsset, to, amount);

        emit SyntheticMinted(syntheticAsset, to, amount);
    }
}

Advantages:

Efficiency: Simpler to manage, all “tokens” are assets, so no need to add special logic for handling deposit to restake pallet. (asset-id already exists)

Disadvantages:

Dependence on Native Features: Tightly coupled with the underlying blockchain architecture.

2. Using token factory contract to mint synthetic asset on evm (not using assets pallet)

In this approach, a custom Token Factory Contract is deployed to manage the creation and minting of synthetic assets directly on the EVM chain without relying on the native Assets Pallet.

// Minimal ERC-20 Interface
interface IERC20 {
    function mint(address to, uint256 amount) external;
    function burn(address from, uint256 amount) external;
}

// Token Factory Contract
contract TokenFactory {
    // Mapping of foreign asset to synthetic token contract
    mapping(address => address) public foreignToSynthetic;

    // Events
    event SyntheticCreated(address indexed foreignAsset, address indexed syntheticToken);
    event SyntheticMinted(address indexed syntheticToken, address indexed to, uint256 amount);
    event SyntheticBurned(address indexed syntheticToken, address indexed from, uint256 amount);

    /**
     * @dev Creates a synthetic token for the given foreign asset if it doesn't already exist.
     * @param foreignAsset The address of the foreign asset.
     * @param name The name of the synthetic token.
     * @param symbol The symbol of the synthetic token.
     */
    function createSynthetic(address foreignAsset, string memory name, string memory symbol) external {
        require(foreignToSynthetic[foreignAsset] == address(0), "Synthetic token already exists");
        
        // Deploy a new synthetic token (ERC-20) contract
        SyntheticToken syntheticToken = new SyntheticToken(name, symbol);
        
        // Map the foreign asset to the synthetic token
        foreignToSynthetic[foreignAsset] = address(syntheticToken);

        emit SyntheticCreated(foreignAsset, address(syntheticToken));
    }

    /**
     * @dev Mints synthetic tokens for a user.
     * @param foreignAsset The address of the foreign asset.
     * @param to The address to receive the synthetic tokens.
     * @param amount The amount of synthetic tokens to mint.
     */
    function mintSynthetic(address foreignAsset, address to, uint256 amount) external {
        address syntheticToken = foreignToSynthetic[foreignAsset];
        require(syntheticToken != address(0), "Synthetic token does not exist");

        // Mint synthetic tokens
        IERC20(syntheticToken).mint(to, amount);

        emit SyntheticMinted(syntheticToken, to, amount);
    }

    /**
     * @dev Burns synthetic tokens from a user.
     * @param foreignAsset The address of the foreign asset.
     * @param from The address holding the synthetic tokens.
     * @param amount The amount of synthetic tokens to burn.
     */
    function burnSynthetic(address foreignAsset, address from, uint256 amount) external {
        address syntheticToken = foreignToSynthetic[foreignAsset];
        require(syntheticToken != address(0), "Synthetic token does not exist");

        // Burn synthetic tokens
        IERC20(syntheticToken).burn(from, amount);

        emit SyntheticBurned(syntheticToken, from, amount);
    }
}

Advantages:

Efficiency: Easier to manage/modify since it only uses solidity features

Disadvantages:

Need to figure out a way to generate an asset-id for all assets

3. Not minting synthetic token but simply maintain a record

Not recommended since we need the pallet to handle records in multiple ways, also slashing become difficult

@1xstj
Copy link
Contributor Author

1xstj commented Nov 12, 2024

Open questions:

  • Best way to represent contract_address => asset_id
  • How to handle slashing

@drewstone
Copy link
Contributor

drewstone commented Nov 13, 2024

My PR in tangle-network/tnt-core#18 already does proposal #2. I don't use a factory pattern/design but it checks whether the asset exists for metadata it relates to (what bridge sent the message, what asset is it) and then creates a new ERC20. This ERC20 will eventually be stored and custodied by the UserVault which makes the interactions with the precompile.

For now it's not implemented ofc how that actually will generate asset IDs for the assets it holds.

@drewstone
Copy link
Contributor

drewstone commented Nov 13, 2024

I also like proposal 2 because 1 does not solve the problem of dealing with bridged ERC20s anyway! Or ERC20s that users created on Tangle, i.e. LRTs that may interact with the system in novel way. Unless you envision adapting proposal 1 to support any future ERC20 on Tangle EVM through some precompile function for allocating an ID.

@1xstj 1xstj moved this from Not Started 🕧 to Building 🏗️ in Webb Universe Nov 18, 2024
@1xstj 1xstj self-assigned this Nov 18, 2024
@github-project-automation github-project-automation bot moved this from Building 🏗️ to Completed ✅ in Webb Universe Nov 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Completed ✅
Development

Successfully merging a pull request may close this issue.

2 participants