Skip to content

Commit

Permalink
Merge branch 'main' into fix/custom-logic-web-example
Browse files Browse the repository at this point in the history
  • Loading branch information
benjamin852 authored Feb 5, 2024
2 parents da2286d + e154a4c commit 6899dda
Show file tree
Hide file tree
Showing 15 changed files with 957 additions and 301 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Support Node.js version 16.x and 18.x
git clone https://github.com/axelarnetwork/axelar-examples.git
```

2. Install dependencies:
2. Navigate to `axelar-examples` and install dependencies:

```bash
npm install
Expand Down
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

0 comments on commit 6899dda

Please sign in to comment.