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

feat: its examples #162

Merged
merged 24 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions examples/evm/its-canonical-token/CanonicalToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { ERC20 } from '@openzeppelin/contracts/token/ERC20/ERC20.sol';

import { Minter } from '@axelar-network/interchain-token-service/contracts/utils/Minter.sol';
/**
* @title InterchainToken
* @notice This contract implements an interchain token which extends InterchainToken functionality.
* @dev This contract also inherits Minter and Implementation logic.
*/
contract CanonicalToken is ERC20, Minter {
uint8 internal immutable decimals_;

uint256 internal constant UINT256_MAX = 2 ** 256 - 1;

/**
* @notice Constructs the InterchainToken contract.
* @dev Makes the implementation act as if it has been setup already to disallow calls to init() (even though that would not achieve anything really).
*/
constructor(string memory name_, string memory symbol_, uint8 decimalsValue) ERC20(name_, symbol_) {
decimals_ = decimalsValue;
_addMinter(msg.sender);
}

function decimals() public view override returns (uint8) {
return decimals_;
}

/**
* @notice Function to mint new tokens.
* @dev Can only be called by the minter address.
* @param account The address that will receive the minted tokens.
* @param amount The amount of tokens to mint.
*/
function mint(address account, uint256 amount) external onlyRole(uint8(Roles.MINTER)) {
_mint(account, amount);
}

/**
* @notice Function to burn tokens.
* @dev Can only be called by the minter address.
* @param account The address that will have its tokens burnt.
* @param amount The amount of tokens to burn.
*/
function burn(address account, uint256 amount) external onlyRole(uint8(Roles.MINTER)) {
_burn(account, amount);
}
}
54 changes: 54 additions & 0 deletions examples/evm/its-canonical-token/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Call Contract Example

This example demonstrates how to relay a message from a source-chain to a destination-chain.

### Prerequisite

Make sure you've already followed the following steps:

- [Setup environment variables](/README.md#set-environment-variables)
- [Run the local chains](/README.md#running-the-local-chains)

### Deployment

To deploy the contract, use the following command:

```bash
npm run deploy evm/call-contract [local|testnet]
```

The aforementioned command pertains to specifying the intended environment for a project to execute on. It provides the option to choose between local and testnet environments by appending either `local` or `testnet` after the command.

An example of its usage is demonstrated as follows: `npm run deploy evm/call-contract local` or `npm run deploy evm/call-contract testnet`.

### Execution

To execute the example, use the following command:

```bash
npm run execute evm/call-contract [local|testnet] ${srcChain} ${destChain} ${message}
```

### Parameters

- `srcChain`: The blockchain network from which the message will be relayed. Acceptable values include "Moonbeam", "Avalanche", "Fantom", "Ethereum", and "Polygon". Default value is Avalanche.
- `destChain`: The blockchain network to which the message will be relayed. Acceptable values include "Moonbeam", "Avalanche", "Fantom", "Ethereum", and "Polygon". Default value is Fantom.
- `message`: The message to be relayed between the chains. Default value is "Hello World".

## Example

This example deploys the contract on a local network and relays a message "Hello World" from Moonbeam to Avalanche.

```bash
npm run deploy evm/call-contract local
npm run execute evm/call-contract local "Fantom" "Avalanche" "Hello World"
```

The output will be:

```
--- Initially ---
value at Avalanche is
--- After ---
value at Avalanche is Hello World
```
58 changes: 58 additions & 0 deletions examples/evm/its-canonical-token/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use strict';

const {
utils: { deployContract },
} = require('@axelar-network/axelar-local-dev');
const { IInterchainTokenService, IInterchainTokenFactory } = require('@axelar-network/axelar-local-dev/dist/contracts');
const { Contract } = require('ethers');
const { interchainTransfer } = require('../../../scripts/libs/its-utils');

const CanonicalToken = rootRequire('./artifacts/examples/evm/its-canonical-token/CanonicalToken.sol/CanonicalToken.json');

async function deploy(chain, wallet) {
console.log(`Deploying CanonicalToken for ${chain.name}.`);
chain.canonicalToken = await deployContract(wallet, CanonicalToken, ['Custon Token', 'CT', 18]);
chain.wallet = wallet;
console.log(`Deployed CanonicalToken for ${chain.name} at ${chain.canonicalToken.address}.`);
}

async function execute(chains, wallet, options) {
const args = options.args || [];
const { source, destination, calculateBridgeFee } = options;

const amount = args[2] || 1000;

const fee = await calculateBridgeFee(source, destination);

const sourceIts = new Contract(source.interchainTokenService, IInterchainTokenService.abi, wallet.connect(source.provider));
const destinationIts = new Contract(destination.interchainTokenService, IInterchainTokenService.abi, wallet.connect(destination.provider));
const sourceFactory = new Contract(source.interchainTokenFactory, IInterchainTokenFactory.abi, wallet.connect(source.provider));

console.log(`Registerring canonical token ${source.canonicalToken.address} at ${source.name}`);
await (await sourceFactory.registerCanonicalInterchainToken(source.canonicalToken.address)).wait();

console.log(`Deploy remote canonical token from ${source.name} to ${destination.name}`);
await (await sourceFactory.deployRemoteCanonicalInterchainToken('', source.canonicalToken.address, destination.name, fee, {value: fee})).wait();

const tokenId = await sourceFactory.canonicalInterchainTokenId(source.canonicalToken.address);
const destinationTokenAddress = await destinationIts.interchainTokenAddress(tokenId);

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
while (await destination.provider.getCode(destinationTokenAddress) == '0x') {
await sleep(1000);
}

console.log(`Minting ${amount} canonical tokens to ${wallet.address}`);
await (await source.canonicalToken.mint(wallet.address, amount)).wait();

console.log(`Approving ${amount} canonical tokens to the token manager`);
await (await source.canonicalToken.approve(source.interchainTokenService, amount)).wait();

await interchainTransfer(source, destination, wallet, tokenId, amount, fee);
}

module.exports = {
deploy,
execute,
};

89 changes: 89 additions & 0 deletions examples/evm/its-custom-token/CustomToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { InterchainTokenStandard } from '@axelar-network/interchain-token-service/contracts/interchain-token/InterchainTokenStandard.sol';
// Any ERC20 implementation can be used here, we chose OZ since it is quite well known.
import { ERC20 } from '@openzeppelin/contracts/token/ERC20/ERC20.sol';

import { Minter } from '@axelar-network/interchain-token-service/contracts/utils/Minter.sol';
/**
* @title InterchainToken
* @notice This contract implements an interchain token which extends InterchainToken functionality.
* @dev This contract also inherits Minter and Implementation logic.
*/
contract CustomToken is InterchainTokenStandard, ERC20, Minter {
uint8 internal immutable decimals_;
bytes32 internal tokenId;
address internal immutable interchainTokenService_;

uint256 internal constant UINT256_MAX = 2 ** 256 - 1;

/**
* @notice Constructs the InterchainToken contract.
* @dev Makes the implementation act as if it has been setup already to disallow calls to init() (even though that would not achieve anything really).
*/
constructor(string memory name_, string memory symbol_, uint8 decimalsValue, address interchainTokenServiceAddress) ERC20(name_, symbol_) {
decimals_ = decimalsValue;
interchainTokenService_ = interchainTokenServiceAddress;

_addMinter(interchainTokenService_);
_addMinter(msg.sender);
}

function decimals() public view override returns (uint8) {
return decimals_;
}

function setTokenId(bytes32 tokenId_) public {
tokenId = tokenId_;
}

/**
* @notice Returns the interchain token service
* @return address The interchain token service contract
*/
function interchainTokenService() public view override returns (address) {
return interchainTokenService_;
}

/**
* @notice Returns the tokenId for this token.
* @return bytes32 The token manager contract.
*/
function interchainTokenId() public view override returns (bytes32) {
return tokenId;
}

/**
* @notice Function to mint new tokens.
* @dev Can only be called by the minter address.
* @param account The address that will receive the minted tokens.
* @param amount The amount of tokens to mint.
*/
function mint(address account, uint256 amount) external onlyRole(uint8(Roles.MINTER)) {
_mint(account, amount);
}

/**
* @notice Function to burn tokens.
* @dev Can only be called by the minter address.
* @param account The address that will have its tokens burnt.
* @param amount The amount of tokens to burn.
*/
function burn(address account, uint256 amount) external onlyRole(uint8(Roles.MINTER)) {
_burn(account, amount);
}

/**
* @notice A method to be overwritten that will decrease the allowance of the `spender` from `sender` by `amount`.
* @dev Needs to be overwritten. This provides flexibility for the choice of ERC20 implementation used. Must revert if allowance is not sufficient.
*/
function _spendAllowance(address sender, address spender, uint256 amount) internal override (ERC20, InterchainTokenStandard) {
uint256 _allowance = allowance(sender, spender);

if (_allowance != UINT256_MAX) {
_approve(sender, spender, _allowance - amount);
}
}
}
59 changes: 59 additions & 0 deletions examples/evm/its-custom-token/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# ITS Custom Token Example

This example demonstrates how to use the ITS with a custom token implementation.

### Prerequisite

Make sure you've already followed the following steps:

- [Setup environment variables](/README.md#set-environment-variables)
- [Run the local chains](/README.md#running-the-local-chains)

### Deployment

To deploy the custom token, use the following command:

```bash
npm run deploy evm/its-custom-token [local|testnet]
```

The aforementioned command pertains to specifying the intended environment for a project to execute on. It provides the option to choose between local and testnet environments by appending either `local` or `testnet` after the command.

An example of its usage is demonstrated as follows: `npm run deploy evm/its-custom-token local` or `npm run deploy evm/its-custom-token testnet`.

### Execution

To execute the example, use the following command:

```bash
npm run execute evm/its-custom-token [local|testnet] ${srcChain} ${destChain} ${amount} ${salt}
```

### Parameters

- `srcChain`: The blockchain network from which the message will be relayed. Acceptable values include "Moonbeam", "Avalanche", "Fantom", "Ethereum", and "Polygon". Default value is Avalanche.
- `destChain`: The blockchain network to which the message will be relayed. Acceptable values include "Moonbeam", "Avalanche", "Fantom", "Ethereum", and "Polygon". Default value is Fantom.
- `amount`: The amount of token to send. The default is 1000.
- `salt`: The 32 byte salt to use for the token. The default is a random salt depending on the proccess that runs the example.

## Example

This example deploys the custom token on a local network, registers it with the Interchain Token Service and sends 1234 of said token from Fantom to Avalanche.

```bash
npm run deploy evm/its-custom-token local
npm run execute evm/its-custom-token local "Fantom" "Avalanche" 1234 0xa457d6C043b7288454773321a440BA8866D47f96D924D4C38a50b2b0698fae46
```

The output will be:

```
Registering custom token at for Fantom
Registering custom token at for Avalanche
Minting 1234 of custom tokens to 0xBa86A5719722B02a5D5e388999C25f3333c7A9fb
--- Initially ---
Balance at Avalanche is 0
Sending 1234 of token 0x8EF758F0D49c53827b47962fA30BDA7e198a4D14 to Avalanche
--- After ---
Balance at Avalanche is 1234
```
61 changes: 61 additions & 0 deletions examples/evm/its-custom-token/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use strict';

const {
utils: { deployContract },
} = require('@axelar-network/axelar-local-dev');
const { IInterchainTokenService } = require('@axelar-network/axelar-local-dev/dist/contracts');
const { Contract } = require('ethers');
const { keccak256, defaultAbiCoder } = require('ethers/lib/utils');
const { interchainTransfer } = require('../../../scripts/libs/its-utils');

const CustomToken = rootRequire('./artifacts/examples/evm/its-custom-token/CustomToken.sol/CustomToken.json');
const ITokenManager = rootRequire('./artifacts/@axelar-network/interchain-token-service/contracts/interfaces/ITokenManager.sol/ITokenManager.json');
const MINT_BURN = 0;

async function deploy(chain, wallet) {
console.log(`Deploying CustomToken for ${chain.name}.`);
chain.customToken = await deployContract(wallet, CustomToken, ['Custon Token', 'CT', 18, chain.interchainTokenService]);
chain.wallet = wallet;
console.log(`Deployed CustomToken for ${chain.name} at ${chain.customToken.address}.`);
}



async function execute(chains, wallet, options) {
const args = options.args || [];
const { source, destination, calculateBridgeFee } = options;

const amount = args[2] || 1000;
const salt = args[3] || keccak256(defaultAbiCoder.encode(['uint256', 'uint256'], [process.pid, process.ppid]));


const fee = await calculateBridgeFee(source, destination);

async function deployTokenManager(chain, salt) {
console.log(`Registering custom token at for ${chain.name}`);

const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, chain.customToken.address]);
const its = new Contract(chain.interchainTokenService, IInterchainTokenService.abi, wallet.connect(chain.provider));
await (await its.deployTokenManager(salt, '', MINT_BURN, params, 0)).wait();
const tokenId = await its.interchainTokenId(wallet.address, salt);
const tokenManagerAddress = await its.tokenManagerAddress(tokenId);
const tokenManager = new Contract(tokenManagerAddress, ITokenManager.abi, wallet.connect(chain.provider));
return tokenManager;
}

const tokenManager = await deployTokenManager(source, salt);
await deployTokenManager(destination, salt);

const tokenId = await tokenManager.interchainTokenId();

console.log(`Minting ${amount} of custom tokens to ${wallet.address}`);
await (await source.customToken.mint(wallet.address, amount)).wait();

await interchainTransfer(source, destination, wallet, tokenId, amount, fee);
}

module.exports = {
deploy,
execute,
};

Loading