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: service token handling #201

Merged
merged 12 commits into from
Dec 3, 2023
3 changes: 1 addition & 2 deletions contracts/InterchainTokenFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -344,9 +344,8 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
function tokenApprove(bytes32 tokenId, uint256 amount) external payable {
address tokenAddress = service.validTokenAddress(tokenId);
IInterchainToken token = IInterchainToken(tokenAddress);
address tokenManager = service.tokenManagerAddress(tokenId);

token.safeCall(abi.encodeWithSelector(token.approve.selector, tokenManager, amount));
token.safeCall(abi.encodeWithSelector(token.approve.selector, service, amount));
}

/**
Expand Down
203 changes: 85 additions & 118 deletions contracts/InterchainTokenService.sol

Large diffs are not rendered by default.

156 changes: 156 additions & 0 deletions contracts/TokenHandler.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { ITokenHandler } from './interfaces/ITokenHandler.sol';
import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol';
import { SafeTokenTransferFrom, SafeTokenCall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol';
import { ReentrancyGuard } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/ReentrancyGuard.sol';

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

/**
* @title ITokenManager Interface
* @notice This interface is responsible for handling tokens before initiating an interchain token transfer, or after receiving one.
*/
contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard {
using SafeTokenTransferFrom for IERC20;
using SafeTokenCall for IERC20;

/**
* @notice This function gives token to a specified address.
* @dev Can only be called by the service.
* @param tokenManagerType The token manager type.
* @param tokenAddress the address of the token to give.
* @param tokenManager the address of the token manager.
* @param to the address of the recepient.
* @return amount The amount of tokens actually given, which will only be different than `amount` in cases where the token takes some on-transfer fee.
*/
// slither-disable-next-line locked-ether
function giveToken(
uint256 tokenManagerType,
address tokenAddress,
address tokenManager,
address to,
uint256 amount
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
) external payable returns (uint256) {
if (tokenManagerType == uint256(TokenManagerType.LOCK_UNLOCK)) {
Foivos marked this conversation as resolved.
Show resolved Hide resolved
_giveTokenLockUnlock(tokenAddress, tokenManager, to, amount);
return amount;
}

if (tokenManagerType == uint256(TokenManagerType.LOCK_UNLOCK_FEE)) {
amount = _giveTokenLockUnlockFee(tokenAddress, tokenManager, to, amount);
return amount;
}

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

revert UnsupportedTokenManagerType(tokenManagerType);
}

/**
* @notice This function gives token to a specified address.
* @dev Can only be called by the service.
* @param tokenManagerType The token manager type.
* @param tokenAddress the address of the token to give.
* @param tokenManager the address of the token manager.
* @param from the address of the provider.
* @return amount The amount of tokens actually given, which will only be different than `amount` in cases where the token takes some on-transfer fee.
*/
// slither-disable-next-line locked-ether
function takeToken(
uint256 tokenManagerType,
address tokenAddress,
address tokenManager,
address from,
uint256 amount
) external payable returns (uint256) {
if (tokenManagerType == uint256(TokenManagerType.LOCK_UNLOCK)) {
Foivos marked this conversation as resolved.
Show resolved Hide resolved
_takeTokenLockUnlock(tokenAddress, tokenManager, from, amount);
return amount;
}

if (tokenManagerType == uint256(TokenManagerType.LOCK_UNLOCK_FEE)) {
amount = _takeTokenLockUnlockFee(tokenAddress, tokenManager, from, amount);
return amount;
}

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

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

revert UnsupportedTokenManagerType(tokenManagerType);
}

function _giveTokenLockUnlock(address tokenAddress, address tokenManager, address to, uint256 amount) internal {
// slither-disable-next-line arbitrary-send-erc20
IERC20(tokenAddress).safeTransferFrom(tokenManager, to, amount);
}

function _takeTokenLockUnlock(address tokenAddress, address tokenManager, address from, uint256 amount) internal {
// slither-disable-next-line arbitrary-send-erc20
IERC20(tokenAddress).safeTransferFrom(from, tokenManager, amount);
}

function _giveTokenLockUnlockFee(
address tokenAddress,
address tokenManager,
address to,
uint256 amount
) internal noReEntrancy returns (uint256) {
uint256 balanceBefore = IERC20(tokenAddress).balanceOf(to);

// slither-disable-next-line arbitrary-send-erc20
IERC20(tokenAddress).safeTransferFrom(tokenManager, to, amount);

uint256 diff = IERC20(tokenAddress).balanceOf(to) - balanceBefore;
if (diff < amount) {
amount = diff;
}

return amount;
}

function _takeTokenLockUnlockFee(
address tokenAddress,
address tokenManager,
address from,
uint256 amount
) internal noReEntrancy returns (uint256) {
uint256 balanceBefore = IERC20(tokenAddress).balanceOf(tokenManager);

// slither-disable-next-line arbitrary-send-erc20
IERC20(tokenAddress).safeTransferFrom(from, tokenManager, amount);

uint256 diff = IERC20(tokenAddress).balanceOf(tokenManager) - balanceBefore;
if (diff < amount) {
amount = diff;
}

return amount;
}

function _giveTokenMintBurn(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 {
IERC20(tokenAddress).safeCall(abi.encodeWithSelector(IERC20MintableBurnable.burn.selector, from, amount));
}

function _takeTokenMintBurnFrom(address tokenAddress, address from, uint256 amount) internal {
IERC20(tokenAddress).safeCall(abi.encodeWithSelector(IERC20BurnableFrom.burnFrom.selector, from, amount));
}
}
23 changes: 15 additions & 8 deletions contracts/interchain-token/BaseInterchainToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
pragma solidity ^0.8.0;

import { IInterchainTokenStandard } from '../interfaces/IInterchainTokenStandard.sol';
import { ITokenManager } from '../interfaces/ITokenManager.sol';
import { IInterchainTokenService } from '../interfaces/IInterchainTokenService.sol';

import { ERC20 } from './ERC20.sol';

Expand All @@ -14,11 +14,18 @@ import { ERC20 } from './ERC20.sol';
*/
abstract contract BaseInterchainToken is IInterchainTokenStandard, ERC20 {
/**
* @notice Getter for the tokenManager used for this token.
* @notice Getter for the tokenId used for this token.
* @dev Needs to be overwritten.
* @return tokenManager_ The TokenManager called to facilitate interchain transfers.
* @return tokenId_ The tokenId that this token is registerred under.
*/
function tokenManager() public view virtual returns (address tokenManager_);
function interchainTokenId() public view virtual returns (bytes32 tokenId_);

/**
* @notice Getter for the interchain token service.
* @dev Needs to be overwritten.
* @return service The address of the interchain token service.
*/
function interchainTokenService() public view virtual returns (address service);

/**
* @notice Implementation of the interchainTransfer method
Expand All @@ -40,8 +47,8 @@ abstract contract BaseInterchainToken is IInterchainTokenStandard, ERC20 {

_beforeInterchainTransfer(msg.sender, destinationChain, recipient, amount, metadata);

ITokenManager tokenManager_ = ITokenManager(tokenManager());
tokenManager_.transmitInterchainTransfer{ value: msg.value }(sender, destinationChain, recipient, amount, metadata);
IInterchainTokenService service = IInterchainTokenService(interchainTokenService());
service.transmitInterchainTransfer{ value: msg.value }(interchainTokenId(), sender, destinationChain, recipient, amount, metadata);
}

/**
Expand Down Expand Up @@ -69,8 +76,8 @@ abstract contract BaseInterchainToken is IInterchainTokenStandard, ERC20 {

_beforeInterchainTransfer(sender, destinationChain, recipient, amount, metadata);

ITokenManager tokenManager_ = ITokenManager(tokenManager());
tokenManager_.transmitInterchainTransfer{ value: msg.value }(sender, destinationChain, recipient, amount, metadata);
IInterchainTokenService service = IInterchainTokenService(interchainTokenService());
service.transmitInterchainTransfer{ value: msg.value }(interchainTokenId(), sender, destinationChain, recipient, amount, metadata);
}

/**
Expand Down
37 changes: 25 additions & 12 deletions contracts/interchain-token/InterchainToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ contract InterchainToken is BaseInterchainToken, ERC20Permit, Distributable, IIn
string public name;
string public symbol;
uint8 public decimals;
address internal tokenManager_;
bytes32 internal tokenId;
address internal immutable interchainTokenService_;

// bytes32(uint256(keccak256('interchain-token-initialized')) - 1);
bytes32 internal constant INITIALIZED_SLOT = 0xc778385ecb3e8cecb82223fa1f343ec6865b2d64c65b0c15c7e8aef225d9e214;
Expand All @@ -30,8 +31,12 @@ contract InterchainToken is BaseInterchainToken, ERC20Permit, Distributable, IIn
* @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() {
constructor(address interchainTokenServiceAddress) {
_initialize();

if (interchainTokenServiceAddress == address(0)) revert InterchainTokenServiceAddressZero();

interchainTokenService_ = interchainTokenServiceAddress;
}

/**
Expand All @@ -54,23 +59,31 @@ contract InterchainToken is BaseInterchainToken, ERC20Permit, Distributable, IIn
}

/**
* @notice Returns the token manager for this token.
* @return address The token manager contract.
* @notice Returns the interchain token service
* @return address The interchain token service contract
*/
function interchainTokenService() public view override(BaseInterchainToken, IInterchainToken) returns (address) {
return interchainTokenService_;
}

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

/**
* @notice Setup function to initialize contract parameters.
* @param tokenManagerAddress The address of the token manager of this token.
* @param tokenId_ The tokenId of the token.
* @param distributor The address of the token distributor.
* @param tokenName The name of the token.
* @param tokenSymbol The symbopl of the token.
* @param tokenDecimals The decimals of the token.
*/
function init(
address tokenManagerAddress,
bytes32 tokenId_,
address distributor,
string calldata tokenName,
string calldata tokenSymbol,
Expand All @@ -80,20 +93,20 @@ contract InterchainToken is BaseInterchainToken, ERC20Permit, Distributable, IIn

_initialize();

if (tokenManagerAddress == address(0)) revert TokenManagerAddressZero();
if (tokenId_ == bytes32(0)) revert TokenIdZero();
if (bytes(tokenName).length == 0) revert TokenNameEmpty();

tokenManager_ = tokenManagerAddress;
name = tokenName;
symbol = tokenSymbol;
decimals = tokenDecimals;
tokenId = tokenId_;

/**
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
* @dev Set the token manager as a distributor to allow it to mint and burn tokens.
* @dev Set the token service as a distributor to allow it to mint and burn tokens.
* Also add the provided address as a distributor. If `address(0)` was provided,
* add it as a distributor to allow anyone to easily check that no custom distributor was set.
*/
_addDistributor(tokenManagerAddress);
_addDistributor(interchainTokenService_);
_addDistributor(distributor);

_setNameHash(tokenName);
Expand Down
5 changes: 0 additions & 5 deletions contracts/interfaces/IBaseTokenManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ interface IBaseTokenManager {
*/
function tokenAddress() external view returns (address);

/**
* @notice A function that should return the implementation type of the token manager.
*/
function implementationType() external pure returns (uint256);

/**
* @notice A function that should return the token address from the init params.
*/
Expand Down
20 changes: 14 additions & 6 deletions contracts/interfaces/IInterchainToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,35 @@ import { IERC20Named } from './IERC20Named.sol';
* @dev Extends IInterchainTokenStandard and IDistributable.
*/
interface IInterchainToken is IInterchainTokenStandard, IDistributable, IERC20MintableBurnable, IERC20Named {
error TokenManagerAddressZero();
error InterchainTokenServiceAddressZero();
error TokenIdZero();
error TokenNameEmpty();
error AlreadyInitialized();

/**
* @notice Getter for the tokenManager used for this token.
* @notice Getter for the interchain token service contract.
* @dev Needs to be overwitten.
* @return tokenManager_ The TokenManager called to facilitate interchain transfers.
* @return interchainTokenServiceAddress The interchain token service address.
*/
function tokenManager() external view returns (address tokenManager_);
function interchainTokenService() external view returns (address interchainTokenServiceAddress);

/**
* @notice Getter for the tokenId used for this token.
* @dev Needs to be overwitten.
* @return tokenId_ The tokenId for this token.
*/
function interchainTokenId() external view returns (bytes32 tokenId_);

/**
* @notice Setup function to initialize contract parameters.
* @param tokenManagerAddress The address of the token manager of this token.
* @param tokenId_ The tokenId of the token.
* @param distributor The address of the token distributor.
* @param tokenName The name of the token.
* @param tokenSymbol The symbopl of the token.
* @param tokenDecimals The decimals of the token.
*/
function init(
address tokenManagerAddress,
bytes32 tokenId_,
address distributor,
string calldata tokenName,
string calldata tokenSymbol,
Expand Down
4 changes: 2 additions & 2 deletions contracts/interfaces/IInterchainTokenDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ interface IInterchainTokenDeployer {
/**
* @notice Deploys a new instance of the InterchainTokenProxy contract.
* @param salt The salt used by Create3Deployer.
* @param tokenManager Address of the token manager.
* @param tokenId tokenId of the token.
* @param distributor Address of the distributor.
* @param name Name of the token.
* @param symbol Symbol of the token.
Expand All @@ -35,7 +35,7 @@ interface IInterchainTokenDeployer {
*/
function deployInterchainToken(
bytes32 salt,
address tokenManager,
bytes32 tokenId,
address distributor,
string calldata name,
string calldata symbol,
Expand Down
Loading
Loading