Skip to content

Commit

Permalink
Merge branch 'main' into feat/service-upgrade-test
Browse files Browse the repository at this point in the history
  • Loading branch information
deanamiel authored Nov 4, 2023
2 parents 7d52ceb + 237907e commit 4147654
Show file tree
Hide file tree
Showing 50 changed files with 765 additions and 1,125 deletions.
4 changes: 2 additions & 2 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ Note that a lot of the design choises were made with supporting non-EVM chains i

### Canonical Bridges

Most current bridge designs aim to get a pre-existing, popular token to different chains that can benefit from the liquidity. When they do so the resulting token, called [`StandardizedToken`](./contracts/utils/StandardizedToken.sol) in this project, will only have basic functionality that enables users to transfer their token and use it with general use smart contracts like De-Fi applications. This is certainly powerful, and has the benefit that as long as the pre-existing `ERC20` implementation and the bridge function properly everything run as expected. We wanted to include this design for the `InterchainTokenService` as well, so deployers can deploy a Canonical Bridge for any token they want, and this can be done only once per pre-existing `ERC20`. Who the deployer is does not matter for this, they just need to pay for the deployment gas, but they do not need to be trusted as they have no special powers over this kind of bridge
Most current bridge designs aim to get a pre-existing, popular token to different chains that can benefit from the liquidity. When they do so the resulting token, called [`InterchainToken`](./contracts/utils/InterchainToken.sol) in this project, will only have basic functionality that enables users to transfer their token and use it with general use smart contracts like De-Fi applications. This is certainly powerful, and has the benefit that as long as the pre-existing `ERC20` implementation and the bridge function properly everything run as expected. We wanted to include this design for the `InterchainTokenService` as well, so deployers can deploy a Canonical Bridge for any token they want, and this can be done only once per pre-existing `ERC20`. Who the deployer is does not matter for this, they just need to pay for the deployment gas, but they do not need to be trusted as they have no special powers over this kind of bridge

### Custom Bridges

Most projects that look to go cross-chain nowadays have more complex needs that the ones covered by Canonical Bridges: they often need custom `ERC20` designs, and will sometimes want to have additional power over the bridge. This is where the `InterchainTokenService` shines, deployers can claim certain `tokenIds` only based on their `address`, and a `salt` they provide, and specify any kind of `TokenManager` to be deployed and either manage an external `ERC20` or a `StandardizedToken`. Users using Custom Bridges need to trust the deployers as they could easily confiscate the funds of users if they wanted to, same as any `ERC20` distributor could confiscate the funds of users. There are currently three kinds of possible `TokenManagers` available, but this number might increase in the future, as we find more potential uses for the `InterchainTokenService`.
Most projects that look to go cross-chain nowadays have more complex needs that the ones covered by Canonical Bridges: they often need custom `ERC20` designs, and will sometimes want to have additional power over the bridge. This is where the `InterchainTokenService` shines, deployers can claim certain `tokenIds` only based on their `address`, and a `salt` they provide, and specify any kind of `TokenManager` to be deployed and either manage an external `ERC20` or a `InterchainToken`. Users using Custom Bridges need to trust the deployers as they could easily confiscate the funds of users if they wanted to, same as any `ERC20` distributor could confiscate the funds of users. There are currently three kinds of possible `TokenManagers` available, but this number might increase in the future, as we find more potential uses for the `InterchainTokenService`.
- Lock/Unlock: This `TokenManager` will simply transfer tokens from a user to itself or vice versa to initiate/fulfill cross-chain transfers
- Mint/Burn: This `TokenManager` will burn/mint tokens from/to the user to initiate/fulfill cross-chain transfers. Tokens used with this kind of `TokenManager` need to be properly permissioned to allow for this behavior.
- Liquidity Pool: This `TokenManager` functions exactly like a Lock/Unlock one, except the balance is kept at a separate, pre-specified account. This allows for deployers to have more control over the bridged funds.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Introduction

This repo will provide an implementation for an Interchain Token Service and an Interchain Token using it. The interchain token service is meant to allow users/developers to easily create their own token bridge. All the underlying cross-chain communication is done by the service, and the deployer can either use a `StandardizedToken` that the service provides, or their own implementations. There are quite a few different possible configurations for bridges, and any user of any deployed bridge needs to trust the deployer of said bridge, much like any user of a token needs to trust the operator of said token. We plan to eventually remove upgradability from the service to make this protocol more trustworthy. Please reference the [design description](./DESIGN.md) for more details on the design. Please look at the [docs](./docs/index.md) for more details on the contracts.
This repo will provide an implementation for an Interchain Token Service and an Interchain Token using it. The interchain token service is meant to allow users/developers to easily create their own token bridge. All the underlying cross-chain communication is done by the service, and the deployer can either use a `InterchainToken` that the service provides, or their own implementations. There are quite a few different possible configurations for bridges, and any user of any deployed bridge needs to trust the deployer of said bridge, much like any user of a token needs to trust the operator of said token. We plan to eventually remove upgradability from the service to make this protocol more trustworthy. Please reference the [design description](./DESIGN.md) for more details on the design. Please look at the [docs](./docs/index.md) for more details on the contracts.

## Build

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pragma solidity ^0.8.0;
import { Ownable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/Ownable.sol';
import { InterchainAddressTracker } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/InterchainAddressTracker.sol';

import { IAddressTracker } from '../interfaces/IAddressTracker.sol';
import { IAddressTracker } from './interfaces/IAddressTracker.sol';

contract AddressTracker is IAddressTracker, Ownable, InterchainAddressTracker {
constructor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,25 @@ import { SafeTokenTransfer, SafeTokenTransferFrom, SafeTokenCall } from '@axelar
import { Multicall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/Multicall.sol';
import { Upgradable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/upgradable/Upgradable.sol';

import { IInterchainTokenService } from '../interfaces/IInterchainTokenService.sol';
import { ITokenRegistrar } from '../interfaces/ITokenRegistrar.sol';
import { ITokenManagerType } from '../interfaces/ITokenManagerType.sol';
import { ITokenManager } from '../interfaces/ITokenManager.sol';
import { IStandardizedToken } from '../interfaces/IStandardizedToken.sol';
import { IInterchainTokenService } from './interfaces/IInterchainTokenService.sol';
import { IInterchainTokenFactory } from './interfaces/IInterchainTokenFactory.sol';
import { ITokenManagerType } from './interfaces/ITokenManagerType.sol';
import { ITokenManager } from './interfaces/ITokenManager.sol';
import { IInterchainToken } from './interfaces/IInterchainToken.sol';

contract TokenRegistrar is ITokenRegistrar, ITokenManagerType, Multicall, Upgradable {
contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, Multicall, Upgradable {
using AddressBytes for bytes;
using AddressBytes for address;
using SafeTokenTransfer for IStandardizedToken;
using SafeTokenTransferFrom for IStandardizedToken;
using SafeTokenCall for IStandardizedToken;

error NotApproved(address tokenAddress);
using SafeTokenTransfer for IInterchainToken;
using SafeTokenTransferFrom for IInterchainToken;
using SafeTokenCall for IInterchainToken;

IInterchainTokenService public immutable service;
bytes32 public immutable chainNameHash;

bytes32 private constant CONTRACT_ID = keccak256('token-registrar');
bytes32 private constant CONTRACT_ID = keccak256('interchain-token-factory');
bytes32 internal constant PREFIX_CANONICAL_TOKEN_SALT = keccak256('canonical-token-salt');
bytes32 internal constant PREFIX_STANDARDIZED_TOKEN_SALT = keccak256('standardized-token-salt');
bytes32 internal constant PREFIX_INTERCHAIN_TOKEN_SALT = keccak256('interchain-token-salt');

constructor(address interchainTokenServiceAddress) {
if (interchainTokenServiceAddress == address(0)) revert ZeroAddress();
Expand All @@ -43,16 +41,24 @@ contract TokenRegistrar is ITokenRegistrar, ITokenManagerType, Multicall, Upgrad
return CONTRACT_ID;
}

function standardizedTokenSalt(bytes32 chainNameHash_, address deployer, bytes32 salt) public pure returns (bytes32) {
return keccak256(abi.encode(PREFIX_STANDARDIZED_TOKEN_SALT, chainNameHash_, deployer, salt));
function interchainTokenSalt(bytes32 chainNameHash_, address deployer, bytes32 salt) public pure returns (bytes32) {
return keccak256(abi.encode(PREFIX_INTERCHAIN_TOKEN_SALT, chainNameHash_, deployer, salt));
}

function standardizedTokenId(address deployer, bytes32 salt) public view returns (bytes32 tokenId) {
tokenId = service.interchainTokenId(address(this), standardizedTokenSalt(chainNameHash, deployer, salt));
function canonicalInterchainTokenSalt(bytes32 chainNameHash_, address tokenAddress) public pure returns (bytes32 salt) {
salt = keccak256(abi.encode(PREFIX_CANONICAL_TOKEN_SALT, chainNameHash_, tokenAddress));
}

function interchainTokenId(address deployer, bytes32 salt) public view returns (bytes32 tokenId) {
tokenId = service.interchainTokenId(address(this), interchainTokenSalt(chainNameHash, deployer, salt));
}

function canonicalInterchainTokenId(address tokenAddress) public view returns (bytes32 tokenId) {
tokenId = service.interchainTokenId(address(this), canonicalInterchainTokenSalt(chainNameHash, tokenAddress));
}

function interchainTokenAddress(address deployer, bytes32 salt) public view returns (address tokenAddress) {
tokenAddress = service.interchainTokenAddress(standardizedTokenId(deployer, salt));
tokenAddress = service.interchainTokenAddress(interchainTokenId(deployer, salt));
}

function deployInterchainToken(
Expand All @@ -61,11 +67,10 @@ contract TokenRegistrar is ITokenRegistrar, ITokenManagerType, Multicall, Upgrad
string calldata symbol,
uint8 decimals,
uint256 mintAmount,
address distributor,
address operator
address distributor
) external payable {
address sender = msg.sender;
salt = standardizedTokenSalt(chainNameHash, sender, salt);
salt = interchainTokenSalt(chainNameHash, sender, salt);
bytes memory distributorBytes;

if (mintAmount > 0) {
Expand All @@ -74,29 +79,32 @@ contract TokenRegistrar is ITokenRegistrar, ITokenManagerType, Multicall, Upgrad
distributorBytes = distributor.toBytes();
}

_deployInterchainToken(salt, '', name, symbol, decimals, distributorBytes, operator.toBytes(), 0);
_deployInterchainToken(salt, '', name, symbol, decimals, distributorBytes, 0);

if (mintAmount > 0) {
bytes32 tokenId = service.interchainTokenId(address(this), salt);
IStandardizedToken token = IStandardizedToken(service.interchainTokenAddress(tokenId));
IInterchainToken token = IInterchainToken(service.interchainTokenAddress(tokenId));
token.mint(address(this), mintAmount);
token.transferDistributorship(distributor);

ITokenManager tokenManager = ITokenManager(service.tokenManagerAddress(tokenId));
tokenManager.removeFlowLimiter(address(this));
if (distributor != address(0)) tokenManager.addFlowLimiter(distributor);
tokenManager.transferOperatorship(distributor);
}
}

function deployRemoteInterchainToken(
string calldata originalChainName,
bytes32 salt,
address additionalDistributor,
address optionalOperator,
string memory destinationChain,
uint256 gasValue
) external payable {
string memory tokenName;
string memory tokenSymbol;
uint8 tokenDecimals;
bytes memory distributor = new bytes(0);
bytes memory operator = new bytes(0);

{
bytes32 chainNameHash_;
Expand All @@ -106,11 +114,10 @@ contract TokenRegistrar is ITokenRegistrar, ITokenManagerType, Multicall, Upgrad
chainNameHash_ = keccak256(bytes(originalChainName));
}
address sender = msg.sender;
salt = standardizedTokenSalt(chainNameHash_, sender, salt);
salt = interchainTokenSalt(chainNameHash_, sender, salt);
bytes32 tokenId = service.interchainTokenId(address(this), salt);

IStandardizedToken token = IStandardizedToken(service.interchainTokenAddress(tokenId));
ITokenManager tokenManager = ITokenManager(service.tokenManagerAddress(tokenId));
IInterchainToken token = IInterchainToken(service.interchainTokenAddress(tokenId));

tokenName = token.name();
tokenSymbol = token.symbol();
Expand All @@ -119,14 +126,9 @@ contract TokenRegistrar is ITokenRegistrar, ITokenManagerType, Multicall, Upgrad
if (!token.isDistributor(additionalDistributor)) revert NotDistributor(additionalDistributor);
distributor = additionalDistributor.toBytes();
}

if (optionalOperator != address(0)) {
if (!tokenManager.isOperator(optionalOperator)) revert NotOperator(optionalOperator);
operator = optionalOperator.toBytes();
}
}

_deployInterchainToken(salt, destinationChain, tokenName, tokenSymbol, tokenDecimals, distributor, operator, gasValue);
_deployInterchainToken(salt, destinationChain, tokenName, tokenSymbol, tokenDecimals, distributor, gasValue);
}

function _deployInterchainToken(
Expand All @@ -136,7 +138,6 @@ contract TokenRegistrar is ITokenRegistrar, ITokenManagerType, Multicall, Upgrad
string memory tokenSymbol,
uint8 tokenDecimals,
bytes memory distributor,
bytes memory operator,
uint256 gasValue
) internal {
// slither-disable-next-line arbitrary-send-eth
Expand All @@ -147,33 +148,24 @@ contract TokenRegistrar is ITokenRegistrar, ITokenManagerType, Multicall, Upgrad
tokenSymbol,
tokenDecimals,
distributor,
operator,
gasValue
);
}

function canonicalTokenSalt(bytes32 chainNameHash_, address tokenAddress) public pure returns (bytes32 salt) {
salt = keccak256(abi.encode(PREFIX_CANONICAL_TOKEN_SALT, chainNameHash_, tokenAddress));
}

function canonicalTokenId(address tokenAddress) public view returns (bytes32 tokenId) {
tokenId = service.interchainTokenId(address(this), canonicalTokenSalt(chainNameHash, tokenAddress));
}

function registerCanonicalToken(address tokenAddress) external payable returns (bytes32 tokenId) {
function registerCanonicalInterchainToken(address tokenAddress) external payable returns (bytes32 tokenId) {
bytes memory params = abi.encode('', tokenAddress);
bytes32 salt = canonicalTokenSalt(chainNameHash, tokenAddress);
bytes32 salt = canonicalInterchainTokenSalt(chainNameHash, tokenAddress);
tokenId = service.deployTokenManager(salt, '', TokenManagerType.LOCK_UNLOCK, params, 0);
}

function deployRemoteCanonicalToken(
function deployRemoteCanonicalInterchainToken(
string calldata originalChain,
address originalTokenAddress,
string calldata destinationChain,
uint256 gasValue
) external payable {
bytes32 salt;
IStandardizedToken token;
IInterchainToken token;

{
bytes32 chainNameHash_;
Expand All @@ -183,18 +175,17 @@ contract TokenRegistrar is ITokenRegistrar, ITokenManagerType, Multicall, Upgrad
chainNameHash_ = keccak256(bytes(originalChain));
}
// This ensures that the token manager has been deployed by this address, so it's safe to trust it.
salt = canonicalTokenSalt(chainNameHash_, originalTokenAddress);
salt = canonicalInterchainTokenSalt(chainNameHash_, originalTokenAddress);
bytes32 tokenId = service.interchainTokenId(address(this), salt);
token = IStandardizedToken(service.tokenAddress(tokenId));
token = IInterchainToken(service.tokenAddress(tokenId));
}

// The 3 lines below will revert if the token does not exist.
string memory tokenName = token.name();
string memory tokenSymbol = token.symbol();
uint8 tokenDecimals = token.decimals();

// slither-disable-next-line arbitrary-send-eth
service.deployInterchainToken{ value: gasValue }(salt, destinationChain, tokenName, tokenSymbol, tokenDecimals, '', '', gasValue);
_deployInterchainToken(salt, destinationChain, tokenName, tokenSymbol, tokenDecimals, '', gasValue);
}

function interchainTransfer(
Expand All @@ -206,7 +197,7 @@ contract TokenRegistrar is ITokenRegistrar, ITokenManagerType, Multicall, Upgrad
) external payable {
if (bytes(destinationChain).length == 0) {
address tokenAddress = service.interchainTokenAddress(tokenId);
IStandardizedToken token = IStandardizedToken(tokenAddress);
IInterchainToken token = IInterchainToken(tokenAddress);
token.safeTransfer(destinationAddress.toAddress(), amount);
} else {
// slither-disable-next-line arbitrary-send-eth
Expand All @@ -216,14 +207,14 @@ contract TokenRegistrar is ITokenRegistrar, ITokenManagerType, Multicall, Upgrad

function tokenTransferFrom(bytes32 tokenId, uint256 amount) external payable {
address tokenAddress = service.tokenAddress(tokenId);
IStandardizedToken token = IStandardizedToken(tokenAddress);
IInterchainToken token = IInterchainToken(tokenAddress);

token.safeTransferFrom(msg.sender, address(this), amount);
}

function tokenApprove(bytes32 tokenId, uint256 amount) external payable {
address tokenAddress = service.tokenAddress(tokenId);
IStandardizedToken token = IStandardizedToken(tokenAddress);
IInterchainToken token = IInterchainToken(tokenAddress);
address tokenManager = service.tokenManagerAddress(tokenId);

token.safeCall(abi.encodeWithSelector(token.approve.selector, tokenManager, amount));
Expand Down
Loading

0 comments on commit 4147654

Please sign in to comment.