-
Notifications
You must be signed in to change notification settings - Fork 163
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: its lock/unlock_fee example (#193)
- Loading branch information
1 parent
6c03476
commit 075dad5
Showing
9 changed files
with
291 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import { InterchainTokenStandard } from '@axelar-network/interchain-token-service/contracts/interchain-token/InterchainTokenStandard.sol'; | ||
import { ERC20 } from '@openzeppelin/contracts/token/ERC20/ERC20.sol'; | ||
|
||
import { Minter } from '@axelar-network/interchain-token-service/contracts/utils/Minter.sol'; | ||
|
||
/** | ||
* @title FeeToken | ||
* @notice This contract implements an interchain token which extends InterchainToken functionality and extracts a fee for each transfer. | ||
* @dev This contract also inherits Minter and Implementation logic. | ||
*/ | ||
contract FeeToken is InterchainTokenStandard, ERC20, Minter { | ||
uint8 internal immutable _decimals; | ||
bytes32 internal tokenId; | ||
address internal immutable _interchainTokenService; | ||
|
||
uint256 internal constant UINT256_MAX = 2 ** 256 - 1; | ||
|
||
uint256 public feePercent; | ||
|
||
/** | ||
* @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, | ||
uint256 _feePercent, | ||
address _interchainTokenServiceAddress | ||
) ERC20(_name, _symbol) { | ||
_decimals = _decimalsValue; | ||
_interchainTokenService = _interchainTokenServiceAddress; | ||
|
||
feePercent = _feePercent; | ||
|
||
_addMinter(msg.sender); | ||
} | ||
|
||
function decimals() public view override returns (uint8) { | ||
return _decimals; | ||
} | ||
|
||
/** | ||
* @notice set new fee percent | ||
* @param _feePercent new fee percent | ||
*/ | ||
function setFeePercent(uint256 _feePercent) external onlyRole(uint8(Roles.MINTER)) { | ||
require(_feePercent <= 100, 'Fee percent too high'); | ||
feePercent = _feePercent; | ||
} | ||
|
||
/** | ||
* @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); | ||
} | ||
|
||
function addMinter(address minter) external { | ||
_addMinter(minter); | ||
} | ||
|
||
/** | ||
* @notice transfer token from sender to recipient. Sender must approve tx to be sent | ||
* @param from sender of the token | ||
* @param to recipient of the token | ||
* @param amount amount of tokens to be sent | ||
*/ | ||
function transferFrom(address from, address to, uint256 amount) public override returns (bool) { | ||
uint256 fee = (amount * feePercent) / 1e18; | ||
uint256 amountAfterFee = amount - fee; | ||
|
||
super.transferFrom(from, to, amountAfterFee); | ||
|
||
/** | ||
* NOTE: Burning of the fee is just one of | ||
* many options here. For simplicity the fee is getting | ||
* burnt, but you can instead for example: | ||
* 1. Send to reward pool | ||
* 2. Send to treasury | ||
* 3. Send to liquidity pool | ||
*/ | ||
_burn(from, fee); | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* @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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# ITS Lock/Unlock_Fee | ||
|
||
This example demonstrates how to use the ITS with a custom token implementation with the `LOCK_UNLOCK_FEE` token manager type. | ||
|
||
The token will be minted/burned on transfers. The token needs to give mint permission to the token manager, but burning happens via an approval. | ||
|
||
### 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-lock-unlock-fee [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-lock-unlock-fee local` or `npm run deploy evm/its-lock-unlock-fee testnet`. | ||
|
||
### Execution | ||
|
||
To execute the example, use the following command: | ||
|
||
```bash | ||
npm run execute evm/its-lock-unlock-fee [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 using a `LOCK_UNLOCK_FEE` token manager type and sends 1234 of said token from Fantom to Avalanche but only receives the amount after fee set in the token itself. | ||
|
||
```bash | ||
npm run deploy evm/its-lock-unlock-fee local | ||
npm run execute evm/its-lock-unlock-fee local "Fantom" "Avalanche" 1234 "0xa457d6C043b7288454773321a440BA8866D47f96D924D4C38a50b2b0698fae46" | ||
``` | ||
|
||
The output will be: | ||
|
||
``` | ||
Registering custom burnable token for Fantom | ||
Approved token for `transferFrom()` | ||
Deploying new manager on dest | ||
Registering custom fee token for Avalanche | ||
Deducting 5% of the tokens sent for the fee | ||
--- Initially --- | ||
Balance at Avalanche is 0 | ||
Sending 1234 of token 0x0EBA33C49c72d2907aFFcacFaC0725409E8b3bc1 to Avalanche | ||
--- After --- | ||
Balance at Avalanche is 1173 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
'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 FeeToken = rootRequire('./artifacts/examples/evm/its-lock-unlock-fee/FeeToken.sol/FeeToken.json'); | ||
|
||
const ITokenManager = rootRequire( | ||
'./artifacts/@axelar-network/interchain-token-service/contracts/interfaces/ITokenManager.sol/ITokenManager.json', | ||
); | ||
const LOCK_UNLOCK_FEE = 3; | ||
const MINT_BURN = 4; | ||
|
||
const lockFee = (5e16).toString(); | ||
async function deploy(chain, wallet) { | ||
console.log(`Deploying Custom Fee Token for ${chain.name}.`); | ||
chain.feeToken = await deployContract(wallet, FeeToken, ['Custom Fee Token', 'CFT', 18, lockFee, chain.interchainTokenService]); | ||
chain.wallet = wallet; | ||
console.log(`Deployed Custom Fee Token for ${chain.name} at ${chain.feeToken.address}.`); | ||
} | ||
|
||
async function execute(chains, wallet, options) { | ||
const args = options.args || []; | ||
const { source, destination, calculateBridgeFee } = options; | ||
|
||
const amount = args[2] || 1e18; | ||
const salt = args[3] || keccak256(defaultAbiCoder.encode(['uint256', 'uint256'], [process.pid, process.ppid])); | ||
|
||
const fee = await calculateBridgeFee(source, destination); | ||
let its; | ||
async function deployTokenManager(chain, tokenManagerType, salt) { | ||
console.log(`Registering custom fee token for ${chain.name}`); | ||
const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, chain.feeToken.address]); | ||
its = new Contract(chain.interchainTokenService, IInterchainTokenService.abi, wallet.connect(chain.provider)); | ||
await (await its.deployTokenManager(salt, '', tokenManagerType, 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)); | ||
await (await chain.feeToken.addMinter(tokenManagerAddress)).wait(); | ||
return tokenManager; | ||
} | ||
|
||
const tokenManager = await deployTokenManager(source, LOCK_UNLOCK_FEE, salt); | ||
const tokenId = await tokenManager.interchainTokenId(); | ||
|
||
await (await source.feeToken.mint(wallet.address, amount)).wait(); | ||
await (await source.feeToken.approve(its.address, amount)).wait(); | ||
|
||
console.log('Approved token for `transferFrom()`'); | ||
console.log('Deploying new manager on dest'); | ||
await deployTokenManager(destination, MINT_BURN, salt); | ||
const feeNumber = Number(lockFee); | ||
const percentage = (feeNumber / 1e18) * 100; | ||
console.log(`Deducting ${percentage}% of the tokens sent for the fee`); | ||
await interchainTransfer(source, destination, wallet, tokenId, amount, fee); | ||
} | ||
|
||
module.exports = { | ||
deploy, | ||
execute, | ||
}; |
Oops, something went wrong.