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.
290 lines
10 KiB
290 lines
10 KiB
// SPDX-License-Identifier: MIT OR Apache-2.0
|
|
pragma solidity >=0.6.11;
|
|
|
|
// ============ Internal Imports ============
|
|
import {BridgeMessage} from "./BridgeMessage.sol";
|
|
import {Encoding} from "./Encoding.sol";
|
|
import {IBridgeToken} from "../../interfaces/bridge/IBridgeToken.sol";
|
|
import {XAppConnectionClient} from "../XAppConnectionClient.sol";
|
|
|
|
// ============ External Imports ============
|
|
import {TypeCasts} from "@celo-org/optics-sol/contracts/XAppConnectionManager.sol";
|
|
import {UpgradeBeaconProxy} from "@celo-org/optics-sol/contracts/upgrade/UpgradeBeaconProxy.sol";
|
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.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 XAppConnectionClient {
|
|
using TypedMemView for bytes;
|
|
using TypedMemView for bytes29;
|
|
using BridgeMessage for bytes29;
|
|
|
|
// ============ Structs ============
|
|
|
|
// 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;
|
|
}
|
|
|
|
// ============ Public Storage ============
|
|
|
|
/// @dev The UpgradeBeacon that new tokens proxies will read implementation
|
|
/// from
|
|
address public tokenBeacon;
|
|
|
|
// 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;
|
|
|
|
// ============ Events ============
|
|
|
|
event TokenDeployed(
|
|
uint32 indexed domain,
|
|
bytes32 indexed id,
|
|
address indexed representation
|
|
);
|
|
|
|
// ======== Initializer =========
|
|
|
|
/**
|
|
* @notice Initialize the TokenRegistry with UpgradeBeaconController and
|
|
* XappConnectionManager.
|
|
* @dev This method deploys two new contracts, and may be expensive to call.
|
|
* @param _xAppConnectionManager The address of the XappConnectionManager
|
|
* that will manage Optics channel connectoins
|
|
*/
|
|
function initialize(address _tokenBeacon, address _xAppConnectionManager)
|
|
public
|
|
initializer
|
|
{
|
|
tokenBeacon = _tokenBeacon;
|
|
XAppConnectionClient._initialize(_xAppConnectionManager);
|
|
}
|
|
|
|
// ======== Modifiers =========
|
|
|
|
modifier typeAssert(bytes29 _view, BridgeMessage.Types _t) {
|
|
_view.assertType(uint40(_t));
|
|
_;
|
|
}
|
|
|
|
// ======== External: Token Lookup Convenience =========
|
|
|
|
/**
|
|
* @notice Looks up the canonical identifier for a local representation.
|
|
* @dev If no such canonical ID is known, this instead returns
|
|
* (0, bytes32(0)).
|
|
* @param _local The local address of the representation
|
|
*/
|
|
function getCanonicalAddress(address _local)
|
|
external
|
|
view
|
|
returns (uint32 _domain, bytes32 _id)
|
|
{
|
|
TokenId memory _canonical = representationToCanonical[_local];
|
|
_domain = _canonical.domain;
|
|
_id = _canonical.id;
|
|
}
|
|
|
|
/**
|
|
* @notice Looks up the local address corresponding to a domain/id pair.
|
|
* @dev If the token is local, it will return the local address.
|
|
* If the token is non-local and no local representation exists, this
|
|
* will return `address(0)`.
|
|
* @param _domain the domain of the canonical version.
|
|
* @param _id the identifier of the canonical version in its domain.
|
|
* @return _token the local address of the token contract
|
|
*/
|
|
function getLocalAddress(uint32 _domain, address _id)
|
|
external
|
|
view
|
|
returns (address _token)
|
|
{
|
|
_token = getLocalAddress(_domain, TypeCasts.addressToBytes32(_id));
|
|
}
|
|
|
|
// ======== Public: Token Lookup Convenience =========
|
|
|
|
/**
|
|
* @notice Looks up the local address corresponding to a domain/id pair.
|
|
* @dev If the token is local, it will return the local address.
|
|
* If the token is non-local and no local representation exists, this
|
|
* will return `address(0)`.
|
|
* @param _domain the domain of the canonical version.
|
|
* @param _id the identifier of the canonical version in its domain.
|
|
* @return _token the local address of the token contract
|
|
*/
|
|
function getLocalAddress(uint32 _domain, bytes32 _id)
|
|
public
|
|
view
|
|
returns (address _token)
|
|
{
|
|
_token = _getTokenAddress(BridgeMessage.formatTokenId(_domain, _id));
|
|
}
|
|
|
|
// ======== Internal Functions =========
|
|
|
|
function _defaultDetails(bytes29 _tokenId)
|
|
internal
|
|
pure
|
|
typeAssert(_tokenId, BridgeMessage.Types.TokenId)
|
|
returns (string memory _name, string memory _symbol)
|
|
{
|
|
// get the first and second half of the token ID
|
|
(uint256 _firstHalfId, uint256 _secondHalfId) = Encoding.encodeHex(
|
|
uint256(_tokenId.id())
|
|
);
|
|
// encode the default token name: "optics.[domain].[id]"
|
|
_name = string(
|
|
abi.encodePacked(
|
|
"optics.",
|
|
Encoding.encodeUint32(_tokenId.domain()),
|
|
".0x",
|
|
_firstHalfId,
|
|
_secondHalfId
|
|
)
|
|
);
|
|
// allocate the memory for a new 32-byte string
|
|
_symbol = new string(32);
|
|
assembly {
|
|
mstore(add(_symbol, 0x20), mload(add(_name, 0x20)))
|
|
}
|
|
}
|
|
|
|
function _cloneTokenContract() internal returns (address result) {
|
|
address _newProxy = address(new UpgradeBeaconProxy(tokenBeacon, ""));
|
|
IBridgeToken(_newProxy).initialize();
|
|
return _newProxy;
|
|
}
|
|
|
|
function _deployToken(bytes29 _tokenId)
|
|
internal
|
|
typeAssert(_tokenId, BridgeMessage.Types.TokenId)
|
|
returns (address _token)
|
|
{
|
|
// deploy the token contract
|
|
_token = address(new UpgradeBeaconProxy(tokenBeacon, ""));
|
|
// set the default token name & symbol
|
|
string memory _name;
|
|
string memory _symbol;
|
|
(_name, _symbol) = _defaultDetails(_tokenId);
|
|
IBridgeToken(_token).setDetails(_name, _symbol, 18);
|
|
// store token in mappings
|
|
representationToCanonical[_token].domain = _tokenId.domain();
|
|
representationToCanonical[_token].id = _tokenId.id();
|
|
canonicalToRepresentation[_tokenId.keccak()] = _token;
|
|
// emit event upon deploying new token
|
|
emit TokenDeployed(_tokenId.domain(), _tokenId.id(), _token);
|
|
}
|
|
|
|
/// @dev Gets the local address corresponding to the canonical ID, or
|
|
/// address(0)
|
|
function _getTokenAddress(bytes29 _tokenId)
|
|
internal
|
|
view
|
|
typeAssert(_tokenId, BridgeMessage.Types.TokenId)
|
|
returns (address _local)
|
|
{
|
|
if (_tokenId.domain() == _localDomain()) {
|
|
// Token is of local origin
|
|
_local = _tokenId.evmId();
|
|
} else {
|
|
// Token is a representation of a token of remote origin
|
|
_local = canonicalToRepresentation[_tokenId.keccak()];
|
|
}
|
|
}
|
|
|
|
function _ensureToken(bytes29 _tokenId)
|
|
internal
|
|
typeAssert(_tokenId, BridgeMessage.Types.TokenId)
|
|
returns (IERC20)
|
|
{
|
|
address _local = _getTokenAddress(_tokenId);
|
|
if (_local == address(0)) {
|
|
// Representation does not exist yet;
|
|
// deploy representation contract
|
|
_local = _deployToken(_tokenId);
|
|
}
|
|
return IERC20(_local);
|
|
}
|
|
|
|
/// @dev returns the token corresponding to the canonical ID, or errors.
|
|
function _mustHaveToken(bytes29 _tokenId)
|
|
internal
|
|
view
|
|
typeAssert(_tokenId, BridgeMessage.Types.TokenId)
|
|
returns (IERC20)
|
|
{
|
|
address _local = _getTokenAddress(_tokenId);
|
|
require(_local != address(0), "!token");
|
|
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 _token) 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[_token].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(_token)
|
|
}
|
|
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));
|
|
}
|
|
}
|
|
|