The home for Hyperlane core contracts, sdk packages, and other infrastructure
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
hyperlane-monorepo/solidity/optics-xapps/contracts/bridge/TokenRegistry.sol

184 lines
6.7 KiB

// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
// ============ Internal Imports ============
import {BridgeMessage} from "./BridgeMessage.sol";
import {BridgeToken} from "./BridgeToken.sol";
import {IBridgeToken} from "../../interfaces/bridge/IBridgeToken.sol";
import {XAppConnectionClient} from "../XAppConnectionClient.sol";
// ============ External Imports ============
test: migrate tests and deploy process to ts (#400) * refactor: migrate js lib to ts * refactor: rewrite extendEnvironment in ts * add: sample tests * add: extend hre * refactor: ts deploy and signer type * refactor: devDeploy for tests * document: isTestDeploy param, add common.test.ts * add: common tests * fix: common tests * clean: remove sample script * refactor: remove Signer type and use ethers.Signer * refactor: typescript directory * build: generate typechain * refactor: typescript code (#399) * refactor: optics-tests/optics-deploy * fix: install typechain * fix * fix: ts errors * add: merkle tests (#417) * add: merkle tests * clean: remove commented code * fix: merkle tests * test: add queue tests (#422) * Add queue tests * fix: add await to fix nonce bug * test: home ts (#401) * add: home tests * add: home tests * add: deploy home (broken) * refactor: add num confirmations to config * refactor: clean up around ts tests (#424) - add testChain.ts with utility functions for making test Chain and Deploy - refactor the Optics hardhat extension - improve typing of enums in Optics lib - remove most references to waffle (prefer ethers) - remove isTestDeploy from deployment args in favor of a test? on Deploys * refactor: clean test deploy, home tests passing * refactor: deployOptics * fix: white space * lint: fix white space * refactor: testCase vectors and imports * fix: rust test vector generation * fix: missing await Co-authored-by: James Prestwich <10149425+prestwich@users.noreply.github.com> * test: add message test (#423) * test: add message test * fix: replace require with import * fix: import for queue.test.ts * enhance: add type check * fix: import conflicts * test: add upgrade tests (#443) * test: add upgrade tests * fix: missing arg * refactor: clean up code * remove: unused code * refactor: deploy proxy * fix: unsaved code * fix: ts conflicts * add: utils ts (#453) * draft * refactor: remove waffle from home tests * fix: types, remove waffle Co-authored-by: yoduyodu <wang7ong@gmail.com> * test: add XAppConnectionManager tests (#456) * test: add XAppConnectionManager tests * test: initial setup for XAppConnectionManager tests * test: most XAppConnectionManager tests passing * fix: failing test * test: SimpleMessage ts (#468) * test: add cross-chain test utils * test: simpleMessage, mostly passing * fix: prove and process test * test: add initial state test, clean up * test: recoverymanager ts (#473) * test: add recoveryManager initial tests * fix: set recoveryManager in ts deploy * fix: set governor * test: replica ts (#483) * test: add replica tests * debug: fix failing tests * WIP: test: governance router/upgrade ts (#470) * test: add governance router tests * debug: some tests * debug: Gov Router tests * refactor: upgradeUtils * fix: missing await * test: clean up ts tests (#484) * clean: imports * refactor: utils * refactor: remove logs during testing * fix: weird bug, ethers.getSigners messes up describe blocks * delete: solidity/optics-core/js * update: pre-commit script for ts * update: pre-commit and scripts * fix: test, update with main * clean: types * fix: bad recipient handle * add: todo for gov router test * add: add back verify deploy stuff in js for now * bug: fix the governance upgrade test (#490) * fix: gov router upgrade test * feature: use TS in both solidity packages * bug: install deps in typescript dir in tests * feature: enhanced github action install * chore: disable automerge, and move lint before test Co-authored-by: Tong Wang <wang7ong@gmail.com> Co-authored-by: James Prestwich <10149425+prestwich@users.noreply.github.com> Co-authored-by: James Prestwich <james@prestwi.ch>
3 years ago
import {XAppConnectionManager, TypeCasts} from "@celo-org/optics-sol/contracts/XAppConnectionManager.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/Initializable.sol";
/**
* @title TokenRegistry
* @notice manages a registry of token contracts on this chain
*
* We sort token types as "representation token" or "locally originating token".
* Locally originating - a token contract that was originally deployed on the local chain
* Representation (repr) - a token that was originally deployed on some other chain
*
* When the router handles an incoming message, it determines whether the
* transfer is for an asset of local origin. If not, it checks for an existing
* representation contract. If no such representation exists, it deploys a new
* representation contract. It then stores the relationship in the
* "reprToCanonical" and "canonicalToRepr" mappings to ensure we can always
* perform a lookup in either direction
* Note that locally originating tokens should NEVER be represented in these lookup tables.
*/
abstract contract TokenRegistry is Initializable, XAppConnectionClient {
using TypedMemView for bytes;
using TypedMemView for bytes29;
using BridgeMessage for bytes29;
// We identify tokens by a TokenId:
// domain - 4 byte chain ID of the chain from which the token originates
// id - 32 byte identifier of the token address on the origin chain, in that chain's address format
struct TokenId {
uint32 domain;
bytes32 id;
}
event TokenDeployed(
uint32 indexed domain,
bytes32 indexed id,
address indexed representation
);
// Contract bytecode that will be cloned to deploy
// new representation token contracts
address internal tokenTemplate;
// local representation token address => token ID
mapping(address => TokenId) public representationToCanonical;
// hash of the tightly-packed TokenId => local representation token address
// If the token is of local origin, this MUST map to address(0).
mapping(bytes32 => address) public canonicalToRepresentation;
// ======== Initializer =========
function _initialize(address _xAppConnectionManager)
internal
override
initializer
{
tokenTemplate = address(new BridgeToken());
XAppConnectionClient._initialize(_xAppConnectionManager);
}
modifier typeAssert(bytes29 _view, BridgeMessage.Types _t) {
_view.assertType(uint40(_t));
_;
}
function setTemplate(address _newTemplate) external onlyOwner {
tokenTemplate = _newTemplate;
}
function _cloneTokenContract() internal returns (address result) {
bytes20 targetBytes = bytes20(tokenTemplate);
// solhint-disable-next-line no-inline-assembly
assembly {
let _clone := mload(0x40)
mstore(
_clone,
0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000
)
mstore(add(_clone, 0x14), targetBytes)
mstore(
add(_clone, 0x28),
0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000
)
result := create(0, _clone, 0x37)
}
}
function _deployToken(bytes29 _tokenId)
internal
typeAssert(_tokenId, BridgeMessage.Types.TokenId)
returns (address _token)
{
// Deploy the token contract by cloning tokenTemplate
_token = _cloneTokenContract();
// Initial details are set to a hash of the ID
bytes32 _idHash = _tokenId.keccak();
IBridgeToken(_token).setDetails(_idHash, _idHash, 18);
// store token in mappings
representationToCanonical[_token].domain = _tokenId.domain();
representationToCanonical[_token].id = _tokenId.id();
canonicalToRepresentation[_idHash] = _token;
// emit event upon deploying new token
emit TokenDeployed(_tokenId.domain(), _tokenId.id(), _token);
}
function _ensureToken(bytes29 _tokenId)
internal
typeAssert(_tokenId, BridgeMessage.Types.TokenId)
returns (IERC20)
{
// Token is of local origin
if (_tokenId.domain() == _localDomain()) {
return IERC20(_tokenId.evmId());
}
// Token is a representation of a token of remote origin
address _local = canonicalToRepresentation[_tokenId.keccak()];
if (_local == address(0)) {
// Representation does not exist yet;
// deploy representation contract
_local = _deployToken(_tokenId);
}
return IERC20(_local);
}
function _tokenIdFor(address _token)
internal
view
returns (TokenId memory _id)
{
_id = representationToCanonical[_token];
if (_id.domain == 0) {
_id.domain = _localDomain();
_id.id = TypeCasts.addressToBytes32(_token);
}
}
function _isLocalOrigin(IERC20 _token) internal view returns (bool) {
return _isLocalOrigin(address(_token));
}
function _isLocalOrigin(address _addr) internal view returns (bool) {
// If the contract WAS deployed by the TokenRegistry,
// it will be stored in this mapping.
// If so, it IS NOT of local origin
if (representationToCanonical[_addr].domain != 0) {
return false;
}
// If the contract WAS NOT deployed by the TokenRegistry,
// and the contract exists, then it IS of local origin
// Return true if code exists at _addr
uint256 _codeSize;
// solhint-disable-next-line no-inline-assembly
assembly {
_codeSize := extcodesize(_addr)
}
return _codeSize != 0;
}
function _reprFor(bytes29 _tokenId)
internal
view
typeAssert(_tokenId, BridgeMessage.Types.TokenId)
returns (IERC20)
{
return IERC20(canonicalToRepresentation[_tokenId.keccak()]);
}
function _downcast(IERC20 _token) internal pure returns (IBridgeToken) {
return IBridgeToken(address(_token));
}
}