basic token bridge design (#71)

* wip: bridge messages

* feature: add a WIP bridge contract for arbitrary tokens

* ref: break out shared tools for devs into UsingOptics.sol

* chore: add a type conversion util method

* refactor: move token registry to its own contract

* refactor: improve isNative using extcodsize

* refactor: update slip44 to domain to match new nomenclature

* doc: remove outdated TODO note

* doc: add a note about the token registry's purpose

* refactor: break out type casting to its own library

* refactor: break Bridge code out into its own package

* chore: make deps explicit in package.json files

* refactor: improve clarity of mustHaveRemote

* rename xfer to transfer (#89)

* bug: fix a copy-paste error in bridge types

Co-authored-by: Anna Carroll <anna-carroll@users.noreply.github.com>
buddies-main-deployment
James Prestwich 4 years ago committed by GitHub
parent c57b9ded00
commit 9ff8490afd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 0
      solidity/optics-bridge/.gitignore
  2. 0
      solidity/optics-bridge/.prettierrc
  3. 0
      solidity/optics-bridge/.solcover.js
  4. 79
      solidity/optics-bridge/contracts/BridgeToken.sol
  5. 328
      solidity/optics-bridge/contracts/OZERC20.sol
  6. 160
      solidity/optics-bridge/contracts/Router.sol
  7. 161
      solidity/optics-bridge/contracts/TokenRegistry.sol
  8. 237
      solidity/optics-bridge/contracts/Types.sol
  9. 27
      solidity/optics-bridge/hardhat.config.js
  10. 35445
      solidity/optics-bridge/package-lock.json
  11. 34
      solidity/optics-bridge/package.json
  12. 0
      solidity/optics-bridge/scripts/index.js
  13. 0
      solidity/optics-bridge/test/index.js
  14. 5
      solidity/optics-core/.gitignore
  15. 23
      solidity/optics-core/.prettierrc
  16. 3
      solidity/optics-core/.solcover.js
  17. 0
      solidity/optics-core/README.md
  18. 0
      solidity/optics-core/contracts/Common.sol
  19. 0
      solidity/optics-core/contracts/Home.sol
  20. 0
      solidity/optics-core/contracts/Merkle.sol
  21. 0
      solidity/optics-core/contracts/Queue.sol
  22. 9
      solidity/optics-core/contracts/Replica.sol
  23. 0
      solidity/optics-core/contracts/Sortition.sol
  24. 85
      solidity/optics-core/contracts/UsingOptics.sol
  25. 4
      solidity/optics-core/contracts/test/MockRecipient.sol
  26. 0
      solidity/optics-core/contracts/test/TestCommon.sol
  27. 0
      solidity/optics-core/contracts/test/TestHome.sol
  28. 0
      solidity/optics-core/contracts/test/TestMerkle.sol
  29. 0
      solidity/optics-core/contracts/test/TestMessage.sol
  30. 0
      solidity/optics-core/contracts/test/TestQueue.sol
  31. 0
      solidity/optics-core/contracts/test/TestReplica.sol
  32. 0
      solidity/optics-core/hardhat.config.js
  33. 0
      solidity/optics-core/lib/Home.abi.json
  34. 0
      solidity/optics-core/lib/ProcessingReplica.abi.json
  35. 0
      solidity/optics-core/lib/index.js
  36. 0
      solidity/optics-core/package-lock.json
  37. 16
      solidity/optics-core/package.json
  38. 0
      solidity/optics-core/scripts/deploy.js
  39. 0
      solidity/optics-core/scripts/index.js
  40. 0
      solidity/optics-core/scripts/process.js
  41. 0
      solidity/optics-core/scripts/update.js
  42. 2
      solidity/optics-core/scripts/update_abis.sh
  43. 0
      solidity/optics-core/scripts/utils.js
  44. 0
      solidity/optics-core/test/Common.test.js
  45. 0
      solidity/optics-core/test/Home.test.js
  46. 0
      solidity/optics-core/test/Merkle.test.js
  47. 0
      solidity/optics-core/test/Message.test.js
  48. 0
      solidity/optics-core/test/Queue.test.js
  49. 0
      solidity/optics-core/test/Replica.test.js
  50. 0
      solidity/optics-core/test/utils.js

@ -0,0 +1,79 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
import {ERC20} from "./OZERC20.sol";
import {TypeCasts} from "@celo-org/optics-sol/contracts/UsingOptics.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
interface BridgeTokenI {
function name() external returns (string memory);
function symbol() external returns (string memory);
function decimals() external returns (uint8);
function burn(address from, uint256 amnt) external;
function mint(address to, uint256 amnt) external;
function setDetails(
bytes32 _name,
bytes32 _symbol,
uint8 _decimals
) external;
}
contract BridgeToken is BridgeTokenI, Ownable, ERC20 {
/**
* @dev Returns the name of the token.
*/
function name() public view override returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view override returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5,05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
* called.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view override returns (uint8) {
return _decimals;
}
function burn(address from, uint256 amnt) external override onlyOwner {
_burn(from, amnt);
}
function mint(address to, uint256 amnt) external override onlyOwner {
_mint(to, amnt);
}
function setDetails(
bytes32 _newName,
bytes32 _newSymbol,
uint8 _newDecimals
) external override onlyOwner {
// careful with naming convention change here
_name = TypeCasts.coerceString(_newName);
_symbol = TypeCasts.coerceString(_newSymbol);
_decimals = _newDecimals;
}
}

@ -0,0 +1,328 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
// This is modified from "@openzeppelin/contracts/token/ERC20/IERC20.sol"
// Modifications were made to make the _name, _symbol, and _decimals fields
// internal instead of private. Getters for them were removed to silence
// solidity inheritance issues
import "@openzeppelin/contracts/GSN/Context.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* We have followed general OpenZeppelin guidelines: functions revert instead
* of returning `false` on failure. This behavior is nonetheless conventional
* and does not conflict with the expectations of ERC20 applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20 is Context, IERC20 {
using SafeMath for uint256;
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string internal _name;
string internal _symbol;
uint8 internal _decimals;
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view override returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `recipient` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address recipient, uint256 amount)
public
virtual
override
returns (bool)
{
_transfer(_msgSender(), recipient, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender)
public
view
virtual
override
returns (uint256)
{
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount)
public
virtual
override
returns (bool)
{
_approve(_msgSender(), spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* Requirements:
*
* - `sender` and `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
* - the caller must have allowance for ``sender``'s tokens of at least
* `amount`.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) public virtual override returns (bool) {
_transfer(sender, recipient, amount);
_approve(
sender,
_msgSender(),
_allowances[sender][_msgSender()].sub(
amount,
"ERC20: transfer amount exceeds allowance"
)
);
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue)
public
virtual
returns (bool)
{
_approve(
_msgSender(),
spender,
_allowances[_msgSender()][spender].add(addedValue)
);
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue)
public
virtual
returns (bool)
{
_approve(
_msgSender(),
spender,
_allowances[_msgSender()][spender].sub(
subtractedValue,
"ERC20: decreased allowance below zero"
)
);
return true;
}
/**
* @dev Moves tokens `amount` from `sender` to `recipient`.
*
* This is internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `sender` cannot be the zero address.
* - `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
*/
function _transfer(
address sender,
address recipient,
uint256 amount
) internal virtual {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(sender, recipient, amount);
_balances[sender] = _balances[sender].sub(
amount,
"ERC20: transfer amount exceeds balance"
);
_balances[recipient] = _balances[recipient].add(amount);
emit Transfer(sender, recipient, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `to` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply = _totalSupply.add(amount);
_balances[account] = _balances[account].add(amount);
emit Transfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
_balances[account] = _balances[account].sub(
amount,
"ERC20: burn amount exceeds balance"
);
_totalSupply = _totalSupply.sub(amount);
emit Transfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(
address owner,
address spender,
uint256 amount
) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Sets {decimals} to a value other than the default one of 18.
*
* WARNING: This function should only be called from the constructor. Most
* applications that interact with token contracts will not expect
* {decimals} to ever change, and may work incorrectly if it does.
*/
function _setupDecimals(uint8 decimals_) internal {
_decimals = decimals_;
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be to transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
}

@ -0,0 +1,160 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
import {BridgeMessage} from "./Types.sol";
import {TokenRegistry} from "./TokenRegistry.sol";
import {BridgeTokenI, BridgeToken} from "./BridgeToken.sol";
import {
TypeCasts,
OpticsHandlerI
} from "@celo-org/optics-sol/contracts/UsingOptics.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
contract BridgeRouter is OpticsHandlerI, TokenRegistry {
using TypedMemView for bytes;
using TypedMemView for bytes29;
using BridgeMessage for bytes29;
using SafeERC20 for IERC20;
mapping(uint32 => bytes32) internal remotes;
constructor() TokenRegistry() {}
function enrollRemote(uint32 _origin, bytes32 _router) external onlyOwner {
remotes[_origin] = _router;
}
function mustHaveRemote(uint32 _domain)
internal
view
returns (bytes32 _remote)
{
_remote = remotes[_domain];
require(_remote != bytes32(0), "!remote");
}
function isRemoteRouter(uint32 _origin, bytes32 _router)
internal
view
returns (bool)
{
return remotes[_origin] == _router;
}
modifier onlyRemoteRouter(uint32 _origin, bytes32 _router) {
require(isRemoteRouter(_origin, _router));
_;
}
function handle(
uint32 _origin,
bytes32 _sender,
bytes memory _message
)
external
override
onlyReplica
onlyRemoteRouter(_origin, _sender)
returns (bytes memory)
{
bytes29 _msg = _message.ref(0).mustBeMessage();
bytes29 _tokenId = _msg.tokenId();
bytes29 _action = _msg.action();
if (_action.isTransfer()) {
return handleTransfer(_tokenId, _action);
}
if (_action.isDetails()) {
return handleDetails(_tokenId, _action);
}
require(false, "!action");
return hex"";
}
function handleTransfer(bytes29 _tokenId, bytes29 _action)
internal
typeAssert(_tokenId, BridgeMessage.Types.TokenId)
typeAssert(_action, BridgeMessage.Types.Transfer)
returns (bytes memory)
{
IERC20 _token = ensureToken(_tokenId);
if (isNative(_token)) {
_token.safeTransfer(_action.evmRecipient(), _action.amnt());
} else {
downcast(_token).mint(_action.evmRecipient(), _action.amnt());
}
return hex"";
}
function handleDetails(bytes29 _tokenId, bytes29 _action)
internal
typeAssert(_tokenId, BridgeMessage.Types.TokenId)
typeAssert(_action, BridgeMessage.Types.Details)
returns (bytes memory)
{
IERC20 _token = ensureToken(_tokenId);
require(!isNative(_token), "!repr");
downcast(_token).setDetails(
_action.name(),
_action.symbol(),
_action.decimals()
);
return hex"";
}
function send(
address _token,
uint32 _destination,
bytes32 _recipient,
uint256 _amnt
) external {
bytes32 _remote = mustHaveRemote(_destination);
IERC20 _tok = IERC20(_token);
if (isNative(_tok)) {
_tok.safeTransferFrom(msg.sender, address(this), _amnt);
} else {
downcast(_tok).burn(msg.sender, _amnt);
}
TokenId memory _tokId = tokenIdFor(_token);
bytes29 _tokenId =
BridgeMessage.formatTokenId(_tokId.domain, _tokId.id);
bytes29 _action = BridgeMessage.formatTransfer(_recipient, _amnt);
home.enqueue(
_destination,
_remote,
BridgeMessage.formatMessage(_tokenId, _action)
);
}
function updateDetails(address _token, uint32 _destination) external {
bytes32 _remote = mustHaveRemote(_destination);
BridgeTokenI _tok = BridgeTokenI(_token);
TokenId memory _tokId = tokenIdFor(_token);
bytes29 _tokenId =
BridgeMessage.formatTokenId(_tokId.domain, _tokId.id);
bytes29 _action =
BridgeMessage.formatDetails(
TypeCasts.coerceBytes32(_tok.name()),
TypeCasts.coerceBytes32(_tok.symbol()),
_tok.decimals()
);
home.enqueue(
_destination,
_remote,
BridgeMessage.formatMessage(_tokenId, _action)
);
}
}

@ -0,0 +1,161 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
import {BridgeMessage} from "./Types.sol";
import {BridgeTokenI, BridgeToken} from "./BridgeToken.sol";
import {
UsingOptics,
TypeCasts
} from "@celo-org/optics-sol/contracts/UsingOptics.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol";
// How the token registry works:
// We sort token types as "representation" or "native".
// Native means a contract that is originally deployed on this chain
// Representation (repr) means a token that originates on some other chain
//
// We identify tokens by a 4 byte chain ID and a 32 byte identifier in that
// chain's native address format. We leave upgradability and management of
// that identity to the token's deployers.
//
// When the router handles an incoming message, it determines whether the
// transfer is for a native asset. If not, it checks for an existing
// representation. If no such representation exists, it deploys a new
// representation token 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 native tokens should NEVER be represented in these lookup tables.
contract TokenRegistry is UsingOptics {
using TypedMemView for bytes;
using TypedMemView for bytes29;
using BridgeMessage for bytes29;
// simplifies the mapping type if we do it this way
struct TokenId {
uint32 domain;
bytes32 id;
}
// We should be able to deploy a new token on demand
address tokenTemplate;
function createClone(address _target) internal returns (address result) {
bytes20 targetBytes = bytes20(_target);
assembly {
let clone := mload(0x40)
mstore(
clone,
0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000
)
mstore(add(clone, 0x14), targetBytes)
mstore(
add(clone, 0x28),
0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000
)
result := create(0, clone, 0x37)
}
}
// lookup tables for tokens.
// Map the local address to the token ID.
mapping(address => TokenId) internal reprToCanonical;
// Map the hash of the tightly-packed token ID to the address
// of the local representation.
//
// If the token is native, this MUST be address(0).
mapping(bytes32 => address) internal canonicalToRepr;
constructor() {
tokenTemplate = address(new BridgeToken());
}
function setTemplate(address _newTemplate) external onlyOwner {
tokenTemplate = _newTemplate;
}
modifier typeAssert(bytes29 _view, BridgeMessage.Types _t) {
_view.assertType(uint40(_t));
_;
}
function downcast(IERC20 _token) internal pure returns (BridgeTokenI) {
return BridgeTokenI(address(_token));
}
function tokenIdFor(address _token)
internal
view
returns (TokenId memory _id)
{
_id = reprToCanonical[_token];
if (_id.domain == 0) {
_id.domain = home.originDomain();
_id.id = TypeCasts.addressToBytes32(_token);
}
}
function isNative(IERC20 _token) internal view returns (bool) {
address _addr = address(_token);
// If this contract deployed it, it isn't native.
if (reprToCanonical[_addr].domain != 0) {
return false;
}
// Avoid returning true for non-existant contracts
uint256 _codeSize;
assembly {
_codeSize := extcodesize(_addr)
}
return _codeSize != 0;
}
function reprFor(bytes29 _tokenId)
internal
view
typeAssert(_tokenId, BridgeMessage.Types.TokenId)
returns (IERC20)
{
return IERC20(canonicalToRepr[_tokenId.keccak()]);
}
function deployToken(bytes29 _tokenId)
internal
typeAssert(_tokenId, BridgeMessage.Types.TokenId)
returns (address _token)
{
bytes32 _idHash = _tokenId.keccak();
_token = createClone(tokenTemplate);
// Initial details are set to a hash of the ID
BridgeTokenI(_token).setDetails(_idHash, _idHash, 18);
reprToCanonical[_token].domain = _tokenId.domain();
reprToCanonical[_token].id = _tokenId.id();
canonicalToRepr[_idHash] = _token;
}
function ensureToken(bytes29 _tokenId)
internal
typeAssert(_tokenId, BridgeMessage.Types.TokenId)
returns (IERC20)
{
// Native
if (_tokenId.domain() == home.originDomain()) {
return IERC20(_tokenId.evmId());
}
// Repr
address _local = canonicalToRepr[_tokenId.keccak()];
if (_local == address(0)) {
// DEPLO
_local = deployToken(_tokenId);
}
return IERC20(_local);
}
}

@ -0,0 +1,237 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
import "@summa-tx/memview-sol/contracts/TypedMemView.sol";
library BridgeMessage {
using TypedMemView for bytes;
using TypedMemView for bytes29;
uint256 private constant TOKEN_ID_LEN = 36;
uint256 private constant TRANSFER_LEN = 64;
uint256 private constant DETAILS_LEN = 65;
enum Types {
Invalid, // 0
Transfer, // 1
Details, // 2
TokenId, // 3
Message // 4
}
modifier typeAssert(bytes29 _view, Types _t) {
_view.assertType(uint40(_t));
_;
}
function messageType(bytes29 _view) internal pure returns (Types) {
return Types(uint8(_view.typeOf()));
}
function isTransfer(bytes29 _view) internal pure returns (bool) {
return messageType(_view) == Types.Transfer;
}
function isDetails(bytes29 _view) internal pure returns (bool) {
return messageType(_view) == Types.Details;
}
function formatTransfer(bytes32 _to, uint256 _amnt)
internal
pure
returns (bytes29)
{
return mustBeTransfer(abi.encodePacked(_to, _amnt).ref(0));
}
function formatDetails(
bytes32 _name,
bytes32 _symbol,
uint8 _decimals
) internal pure returns (bytes29) {
return
mustBeDetails(abi.encodePacked(_name, _symbol, _decimals).ref(0));
}
function formatTokenId(uint32 _domain, bytes32 _id)
internal
pure
returns (bytes29)
{
return mustBeTokenId(abi.encodePacked(_domain, _id).ref(0));
}
function formatMessage(bytes29 _tokenId, bytes29 _action)
internal
view
typeAssert(_tokenId, Types.TokenId)
returns (bytes memory)
{
require(isDetails(_action) || isTransfer(_action), "!action");
bytes29[] memory _views = new bytes29[](2);
_views[0] = _tokenId;
_views[1] = _action;
return TypedMemView.join(_views);
}
function domain(bytes29 _view)
internal
pure
typeAssert(_view, Types.TokenId)
returns (uint32)
{
return uint32(_view.indexUint(0, 4));
}
function id(bytes29 _view)
internal
pure
typeAssert(_view, Types.TokenId)
returns (bytes32)
{
return _view.index(4, 32);
}
function evmId(bytes29 _view)
internal
pure
typeAssert(_view, Types.TokenId)
returns (address)
{
// 4 bytes domain + 12 empty to trim for address
return _view.indexAddress(16);
}
function recipient(bytes29 _view)
internal
pure
typeAssert(_view, Types.Transfer)
returns (bytes32)
{
return _view.index(0, 32);
}
function evmRecipient(bytes29 _view)
internal
pure
typeAssert(_view, Types.Transfer)
returns (address)
{
return _view.indexAddress(12);
}
function amnt(bytes29 _view)
internal
pure
typeAssert(_view, Types.Transfer)
returns (uint256)
{
return _view.indexUint(32, 32);
}
function name(bytes29 _view)
internal
pure
typeAssert(_view, Types.Details)
returns (bytes32)
{
return _view.index(0, 32);
}
function symbol(bytes29 _view)
internal
pure
typeAssert(_view, Types.Details)
returns (bytes32)
{
return _view.index(32, 32);
}
function decimals(bytes29 _view)
internal
pure
typeAssert(_view, Types.Details)
returns (uint8)
{
return uint8(_view.indexUint(64, 1));
}
function tokenId(bytes29 _view)
internal
pure
typeAssert(_view, Types.Message)
returns (bytes29)
{
return _view.slice(0, TOKEN_ID_LEN, uint40(Types.TokenId));
}
function action(bytes29 _view)
internal
pure
typeAssert(_view, Types.Message)
returns (bytes29)
{
if (_view.len() == TOKEN_ID_LEN + DETAILS_LEN) {
return
_view.slice(
TOKEN_ID_LEN,
TOKEN_ID_LEN + DETAILS_LEN,
uint40(Types.Details)
);
}
return
_view.slice(
TOKEN_ID_LEN,
TOKEN_ID_LEN + TRANSFER_LEN,
uint40(Types.Transfer)
);
}
function tryAsTransfer(bytes29 _view) internal pure returns (bytes29) {
if (_view.len() == TRANSFER_LEN) {
return _view.castTo(uint40(Types.Transfer));
}
return TypedMemView.nullView();
}
function tryAsDetails(bytes29 _view) internal pure returns (bytes29) {
if (_view.len() == DETAILS_LEN) {
return _view.castTo(uint40(Types.Details));
}
return TypedMemView.nullView();
}
function tryAsTokenId(bytes29 _view) internal pure returns (bytes29) {
if (_view.len() == 36) {
return _view.castTo(uint40(Types.TokenId));
}
return TypedMemView.nullView();
}
function tryAsMessage(bytes29 _view) internal pure returns (bytes29) {
uint256 _len = _view.len();
if (
_len == TOKEN_ID_LEN + TRANSFER_LEN ||
_len == TOKEN_ID_LEN + DETAILS_LEN
) {
return _view.castTo(uint40(Types.Message));
}
return TypedMemView.nullView();
}
function mustBeTransfer(bytes29 _view) internal pure returns (bytes29) {
return tryAsTransfer(_view).assertValid();
}
function mustBeDetails(bytes29 _view) internal pure returns (bytes29) {
return tryAsDetails(_view).assertValid();
}
function mustBeTokenId(bytes29 _view) internal pure returns (bytes29) {
return tryAsTokenId(_view).assertValid();
}
function mustBeMessage(bytes29 _view) internal pure returns (bytes29) {
return tryAsMessage(_view).assertValid();
}
}

@ -0,0 +1,27 @@
require('hardhat-gas-reporter');
require('solidity-coverage');
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: {
version: '0.7.6',
settings: {
optimizer: {
enabled: true,
runs: 999999,
},
},
},
gasReporter: {
currency: 'USD',
},
networks: {
localhost: {
url: 'http://localhost:8545',
},
},
};

File diff suppressed because it is too large Load Diff

@ -0,0 +1,34 @@
{
"name": "@celo-org/optics-bridge-sol",
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.1",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"chai": "^4.3.0",
"ethereum-waffle": "^3.2.2",
"ethers": "^5.0.29",
"hardhat": "^2.0.8",
"hardhat-gas-reporter": "^1.0.4",
"prettier": "^2.2.1",
"prettier-plugin-solidity": "^1.0.0-beta.4",
"solidity-coverage": "^0.7.14"
},
"version": "0.0.0",
"description": "A bridge using optics",
"main": " ",
"directories": {
"test": "test"
},
"scripts": {
"prettier": "prettier --write './contracts' ./test ./scripts",
"compile": "hardhat compile && npm run prettier",
"coverage": "npm run compile && hardhat coverage",
"test": "npm run compile && hardhat test"
},
"author": "James Prestwich",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@celo-org/optics-sol": "file:../optics-core",
"@openzeppelin/contracts": "^3.4.0",
"@summa-tx/memview-sol": "^1.1.1"
}
}

@ -0,0 +1,5 @@
node_modules/
cache/
artifacts/
coverage/
coverage.json

@ -0,0 +1,23 @@
{
"overrides": [
{
"files": "*.js",
"options": {
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "all"
}
},
{
"files": "*.sol",
"options": {
"printWidth": 80,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": false,
"explicitTypes": "always"
}
}
]
}

@ -0,0 +1,3 @@
module.exports = {
skipFiles: ["test"],
};

@ -5,14 +5,7 @@ import "@summa-tx/memview-sol/contracts/TypedMemView.sol";
import "./Common.sol";
import "./Merkle.sol";
import "./Queue.sol";
interface OpticsHandlerI {
function handle(
uint32 origin,
bytes32 sender,
bytes memory message
) external returns (bytes memory);
}
import {OpticsHandlerI} from "./UsingOptics.sol";
abstract contract Replica is Common, QueueManager {
using QueueLib for QueueLib.Queue;

@ -0,0 +1,85 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
import "./Home.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@summa-tx/memview-sol/contracts/TypedMemView.sol";
interface OpticsHandlerI {
function handle(
uint32 origin,
bytes32 sender,
bytes memory message
) external returns (bytes memory);
}
abstract contract UsingOptics is Ownable {
mapping(address => uint32) public replicas;
Home home;
constructor() Ownable() {}
function isReplica(address _replica) internal view returns (bool) {
return replicas[_replica] != 0;
}
function enrollReplica(uint32 _domain, address _replica) public onlyOwner {
replicas[_replica] = _domain;
}
function unenrollReplica(address _replica) public onlyOwner {
replicas[_replica] = 0;
}
function setHome(address _home) public onlyOwner {
home = Home(_home);
}
modifier onlyReplica() {
require(isReplica(msg.sender), "!replica");
_;
}
}
library TypeCasts {
using TypedMemView for bytes;
using TypedMemView for bytes29;
function coerceBytes32(string memory _s)
internal
pure
returns (bytes32 _b)
{
_b = bytes(_s).ref(0).index(0, uint8(bytes(_s).length));
}
// treat it as a null-terminated string of max 32 bytes
function coerceString(bytes32 _buf)
internal
pure
returns (string memory _newStr)
{
uint8 _slen = 0;
while (_slen < 32 && _buf[_slen] != 0) {
_slen++;
}
assembly {
_newStr := mload(0x40)
mstore(0x40, add(_newStr, 0x40)) // may end up with extra
mstore(_newStr, _slen)
mstore(add(_newStr, 0x20), _buf)
}
}
// alignment preserving cast
function addressToBytes32(address _addr) internal pure returns (bytes32) {
return bytes32(uint256(uint160(_addr)));
}
// alignment preserving cast
function bytes32ToAddress(bytes32 _buf) internal pure returns (address) {
return address(uint160(uint256(_buf)));
}
}

@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
import {OpticsHandlerI} from "../Replica.sol";
import {OpticsHandlerI} from "../UsingOptics.sol";
contract MockRecipient is OpticsHandlerI {
constructor() {}
@ -10,7 +10,7 @@ contract MockRecipient is OpticsHandlerI {
uint32,
bytes32,
bytes memory
) external override returns (bytes memory) {
) external pure override returns (bytes memory) {
return bytes(message());
}

@ -3,8 +3,6 @@
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.1",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@openzeppelin/contracts": "^3.4.0",
"@summa-tx/memview-sol": "^1.1.1",
"chai": "^4.3.0",
"ethereum-waffle": "^3.2.2",
"ethers": "^5.0.29",
@ -21,11 +19,15 @@
"test": "test"
},
"scripts": {
"prettier": "prettier --write './contracts' ./test ./scripts",
"compile": "hardhat compile && ./scripts/update_abis.sh",
"coverage": "hardhat coverage",
"test": "hardhat test"
"prettier": "prettier --write ./contracts ./test ./scripts ./lib",
"compile": "hardhat compile && npm run prettier && ./scripts/update_abis.sh",
"coverage": "npm run compile && hardhat coverage",
"test": "npm run compile && hardhat test"
},
"author": "James Prestwich",
"license": "MIT OR Apache-2.0"
"license": "MIT OR Apache-2.0",
"dependencies": {
"@openzeppelin/contracts": "^3.4.0",
"@summa-tx/memview-sol": "^1.1.1"
}
}

@ -2,4 +2,4 @@
cat artifacts/contracts/Replica.sol/ProcessingReplica.json| jq .abi > ./lib/ProcessingReplica.abi.json && \
cat artifacts/contracts/Home.sol/Home.json| jq .abi > ./lib/Home.abi.json && \
cp ./lib/*.json ../rust/optics-base/src/abis/
cp ./lib/*.json ../../rust/optics-base/src/abis/
Loading…
Cancel
Save