Skip to content

Commit

Permalink
feat: added new token managers that have the mint/burn permissions. (#1)
Browse files Browse the repository at this point in the history
* added new token managers that have the mint/burn permissions.

* added a few tests

* a few more tests

* removed mint/burn from legacy

* rename INTERCHAIN_TOKEN to INTERCHAIN_TOKEN_MINT_BURN

* remove unused import and prettier

* some more renaming

* rever

* added some docstrings

* prettier

* chore: restrict custom token manager deployments (#3)

* some styling

* fixed revert

* fixed some docs

* bump package version

* restrict remote token manager deployment

* rename MINT_BURN_FROM to CUSTOM_MINT_BURN_FROM

* prettier

* add comments to TokenManagerType

* rename token manager types

* update docs

* refactor(TokenManager): simplification (#4)

* refactor(TokenManager): simplification

* refactor(TokenManager): unused import

* update doc

---------

Co-authored-by: Milap Sheth <[email protected]>
Co-authored-by: re1ro <[email protected]>
  • Loading branch information
3 people authored Mar 24, 2024
1 parent 78173e3 commit 023083e
Show file tree
Hide file tree
Showing 15 changed files with 344 additions and 91 deletions.
22 changes: 15 additions & 7 deletions contracts/InterchainTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ contract InterchainTokenService is
* part of a multicall involving multiple functions that could make remote contract calls.
* @param salt The salt to be used during deployment.
* @param destinationChain The name of the chain to deploy the TokenManager and standardized token to.
* @param tokenManagerType The type of TokenManager to be deployed.
* @param tokenManagerType The type of token manager to be deployed. Cannot be NATIVE_INTERCHAIN_TOKEN.
* @param params The params that will be used to initialize the TokenManager.
* @param gasValue The amount of native tokens to be used to pay for gas for the remote deployment.
* @return tokenId The tokenId corresponding to the deployed TokenManager.
Expand All @@ -280,12 +280,13 @@ contract InterchainTokenService is
bytes calldata params,
uint256 gasValue
) external payable whenNotPaused returns (bytes32 tokenId) {
// Custom token managers can't be deployed with Interchain token mint burn type, which is reserved for interchain tokens
if (tokenManagerType == TokenManagerType.NATIVE_INTERCHAIN_TOKEN) revert CannotDeploy(tokenManagerType);

address deployer = msg.sender;

if (deployer == interchainTokenFactory) {
deployer = TOKEN_FACTORY_DEPLOYER;
} else {
revert('');
}

tokenId = interchainTokenId(deployer, salt);
Expand Down Expand Up @@ -331,7 +332,7 @@ contract InterchainTokenService is
if (bytes(destinationChain).length == 0) {
address tokenAddress = _deployInterchainToken(tokenId, minter, name, symbol, decimals);

_deployTokenManager(tokenId, TokenManagerType.MINT_BURN, abi.encode(minter, tokenAddress));
_deployTokenManager(tokenId, TokenManagerType.NATIVE_INTERCHAIN_TOKEN, abi.encode(minter, tokenAddress));
} else {
_deployRemoteInterchainToken(tokenId, name, symbol, decimals, minter, destinationChain, gasValue);
}
Expand Down Expand Up @@ -738,8 +739,15 @@ contract InterchainTokenService is
/**
* @notice Processes a deploy token manager payload.
*/
function _processDeployTokenManagerPayload(bytes calldata) internal pure {
revert('');
function _processDeployTokenManagerPayload(bytes calldata payload) internal {
(, bytes32 tokenId, TokenManagerType tokenManagerType, bytes memory params) = abi.decode(
payload,
(uint256, bytes32, TokenManagerType, bytes)
);

if (tokenManagerType == TokenManagerType.NATIVE_INTERCHAIN_TOKEN) revert CannotDeploy(tokenManagerType);

_deployTokenManager(tokenId, tokenManagerType, params);
}

/**
Expand All @@ -755,7 +763,7 @@ contract InterchainTokenService is

tokenAddress = _deployInterchainToken(tokenId, minterBytes, name, symbol, decimals);

_deployTokenManager(tokenId, TokenManagerType.MINT_BURN, abi.encode(minterBytes, tokenAddress));
_deployTokenManager(tokenId, TokenManagerType.NATIVE_INTERCHAIN_TOKEN, abi.encode(minterBytes, tokenAddress));
}

/**
Expand Down
32 changes: 26 additions & 6 deletions contracts/TokenHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { SafeTokenTransferFrom, SafeTokenCall } from '@axelar-network/axelar-gmp
import { ReentrancyGuard } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/ReentrancyGuard.sol';

import { ITokenManagerType } from './interfaces/ITokenManagerType.sol';
import { ITokenManager } from './interfaces/ITokenManager.sol';
import { IERC20MintableBurnable } from './interfaces/IERC20MintableBurnable.sol';
import { IERC20BurnableFrom } from './interfaces/IERC20BurnableFrom.sol';

Expand Down Expand Up @@ -36,8 +37,13 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard {
address to,
uint256 amount
) external payable returns (uint256) {
if (tokenManagerType == uint256(TokenManagerType.NATIVE_INTERCHAIN_TOKEN)) {
_giveInterchainToken(tokenAddress, to, amount);
return amount;
}

if (tokenManagerType == uint256(TokenManagerType.MINT_BURN) || tokenManagerType == uint256(TokenManagerType.MINT_BURN_FROM)) {
_giveTokenMintBurn(tokenAddress, to, amount);
_mintToken(tokenManager, tokenAddress, to, amount);
return amount;
}

Expand Down Expand Up @@ -71,13 +77,18 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard {
address from,
uint256 amount
) external payable returns (uint256) {
if (tokenManagerType == uint256(TokenManagerType.NATIVE_INTERCHAIN_TOKEN)) {
_takeInterchainToken(tokenAddress, from, amount);
return amount;
}

if (tokenManagerType == uint256(TokenManagerType.MINT_BURN)) {
_takeTokenMintBurn(tokenAddress, from, amount);
_burnToken(tokenManager, tokenAddress, from, amount);
return amount;
}

if (tokenManagerType == uint256(TokenManagerType.MINT_BURN_FROM)) {
_takeTokenMintBurnFrom(tokenAddress, from, amount);
_burnTokenFrom(tokenAddress, from, amount);
return amount;
}

Expand Down Expand Up @@ -112,6 +123,7 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard {
uint256 amount
) external payable returns (uint256) {
if (
tokenManagerType == uint256(TokenManagerType.NATIVE_INTERCHAIN_TOKEN) ||
tokenManagerType == uint256(TokenManagerType.LOCK_UNLOCK) ||
tokenManagerType == uint256(TokenManagerType.MINT_BURN) ||
tokenManagerType == uint256(TokenManagerType.MINT_BURN_FROM)
Expand Down Expand Up @@ -151,15 +163,23 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard {
return amount;
}

function _giveTokenMintBurn(address tokenAddress, address to, uint256 amount) internal {
function _giveInterchainToken(address tokenAddress, address to, uint256 amount) internal {
IERC20(tokenAddress).safeCall(abi.encodeWithSelector(IERC20MintableBurnable.mint.selector, to, amount));
}

function _takeTokenMintBurn(address tokenAddress, address from, uint256 amount) internal {
function _takeInterchainToken(address tokenAddress, address from, uint256 amount) internal {
IERC20(tokenAddress).safeCall(abi.encodeWithSelector(IERC20MintableBurnable.burn.selector, from, amount));
}

function _takeTokenMintBurnFrom(address tokenAddress, address from, uint256 amount) internal {
function _mintToken(address tokenManager, address tokenAddress, address to, uint256 amount) internal {
ITokenManager(tokenManager).mintToken(tokenAddress, to, amount);
}

function _burnToken(address tokenManager, address tokenAddress, address from, uint256 amount) internal {
ITokenManager(tokenManager).burnToken(tokenAddress, from, amount);
}

function _burnTokenFrom(address tokenAddress, address from, uint256 amount) internal {
IERC20(tokenAddress).safeCall(abi.encodeWithSelector(IERC20BurnableFrom.burnFrom.selector, from, amount));
}
}
3 changes: 2 additions & 1 deletion contracts/interfaces/IInterchainTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ interface IInterchainTokenService is
error GiveTokenFailed(bytes data);
error TokenHandlerFailed(bytes data);
error EmptyData();
error CannotDeploy(TokenManagerType);

event InterchainTransfer(
bytes32 indexed tokenId,
Expand Down Expand Up @@ -165,7 +166,7 @@ interface IInterchainTokenService is
* @notice Deploys a custom token manager contract on a remote chain.
* @param salt The salt used for token manager deployment.
* @param destinationChain The name of the destination chain.
* @param tokenManagerType The type of token manager.
* @param tokenManagerType The type of token manager. Cannot be NATIVE_INTERCHAIN_TOKEN.
* @param params The deployment parameters.
* @param gasValue The gas value for deployment.
* @return tokenId The tokenId associated with the token manager.
Expand Down
18 changes: 18 additions & 0 deletions contracts/interfaces/ITokenManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,22 @@ interface ITokenManager is IBaseTokenManager, IOperator, IFlowLimit, IImplementa
* @return params_ The resulting params to be passed to custom TokenManager deployments.
*/
function params(bytes calldata operator_, address tokenAddress_) external pure returns (bytes memory params_);

/**
* @notice External function to allow the service to mint tokens through the tokenManager
* @dev This function should revert if called by anyone but the service.
* @param tokenAddress_ The address of the token, since its cheaper to pass it in instead of reading it as the token manager.
* @param to The recipient.
* @param amount The amount to mint.
*/
function mintToken(address tokenAddress_, address to, uint256 amount) external;

/**
* @notice External function to allow the service to burn tokens through the tokenManager
* @dev This function should revert if called by anyone but the service.
* @param tokenAddress_ The address of the token, since its cheaper to pass it in instead of reading it as the token manager.
* @param from The address to burn the token from.
* @param amount The amount to burn.
*/
function burnToken(address tokenAddress_, address from, uint256 amount) external;
}
9 changes: 5 additions & 4 deletions contracts/interfaces/ITokenManagerType.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ pragma solidity ^0.8.0;
*/
interface ITokenManagerType {
enum TokenManagerType {
MINT_BURN,
MINT_BURN_FROM,
LOCK_UNLOCK,
LOCK_UNLOCK_FEE
NATIVE_INTERCHAIN_TOKEN, // This type is reserved for interchain tokens deployed by ITS, and can't be used by custom token managers.
MINT_BURN_FROM, // 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.
LOCK_UNLOCK, // The token will be locked/unlocked at the token manager.
LOCK_UNLOCK_FEE, // The token will be locked/unlocked at the token manager, which will account for any fee-on-transfer behaviour.
MINT_BURN // The token will be minted/burned on transfers. The token needs to give mint and burn permission to the token manager.
}
}
2 changes: 1 addition & 1 deletion contracts/test/TestInterchainTokenStandard.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ contract TestInterchainTokenStandard is InterchainTokenStandard, Minter, ERC20,
_burn(account, amount);
}

function burnFrom(address account, uint256 amount) external onlyRole(uint8(Roles.MINTER)) {
function burnFrom(address account, uint256 amount) external {
uint256 currentAllowance = allowance[account][msg.sender];
if (currentAllowance < amount) revert AllowanceExceeded();
_approve(account, msg.sender, currentAllowance - amount);
Expand Down
23 changes: 23 additions & 0 deletions contracts/token-manager/TokenManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Implementation } from '@axelar-network/axelar-gmp-sdk-solidity/contract
import { SafeTokenCall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol';

import { ITokenManager } from '../interfaces/ITokenManager.sol';
import { IERC20MintableBurnable } from '../interfaces/IERC20MintableBurnable.sol';

import { Operator } from '../utils/Operator.sol';
import { FlowLimit } from '../utils/FlowLimit.sol';
Expand Down Expand Up @@ -179,4 +180,26 @@ contract TokenManager is ITokenManager, Operator, FlowLimit, Implementation {
function params(bytes calldata operator_, address tokenAddress_) external pure returns (bytes memory params_) {
params_ = abi.encode(operator_, tokenAddress_);
}

/**
* @notice External function to allow the service to mint tokens through the tokenManager
* @dev This function should revert if called by anyone but the service.
* @param tokenAddress_ The address of the token, since its cheaper to pass it in instead of reading it as the token manager.
* @param to The recipient.
* @param amount The amount to mint.
*/
function mintToken(address tokenAddress_, address to, uint256 amount) external onlyService {
IERC20(tokenAddress_).safeCall(abi.encodeWithSelector(IERC20MintableBurnable.mint.selector, to, amount));
}

/**
* @notice External function to allow the service to burn tokens through the tokenManager
* @dev This function should revert if called by anyone but the service.
* @param tokenAddress_ The address of the token, since its cheaper to pass it in instead of reading it as the token manager.
* @param from The address to burn the token from.
* @param amount The amount to burn.
*/
function burnToken(address tokenAddress_, address from, uint256 amount) external onlyService {
IERC20(tokenAddress_).safeCall(abi.encodeWithSelector(IERC20MintableBurnable.burn.selector, from, amount));
}
}
Loading

0 comments on commit 023083e

Please sign in to comment.