From 067817f89115036ad3630e1a1cec75ff260b98b3 Mon Sep 17 00:00:00 2001 From: Erin Hales <36751902+ErinHales@users.noreply.github.com> Date: Wed, 18 Aug 2021 14:30:09 -0600 Subject: [PATCH] refactor: extend bridge testing utils (#568) * refactor: split hardhat utils into core/bridge folders * add: bridge utils to hre * clean: remove commented code * fix: BridgeMessageTypes enum * refactor: optics hre object * refactor: hre bridge object * refactor: export bridge functions * fix: add return types, add formatMessage * add: serialize message functions * fix: message --> action, add Message type * add: serialize functions * refactor: use TransferAction type to serialize Message * update: bridge tests to use action/message types * fix: testTokenId * fix: ETH Helper tests * clean: clean up imports --- resolve-typechain-conflicts.sh | 2 - .../src/bridge/TestBridgeDeploy.ts | 18 +- typescript/optics-tests/lib/bridge.ts | 117 ++++++ typescript/optics-tests/lib/core.ts | 201 +++++++++++ typescript/optics-tests/lib/index.ts | 201 +---------- typescript/optics-tests/lib/permit.ts | 3 +- typescript/optics-tests/lib/types.ts | 58 ++- .../test/bridge/BridgeToken.test.ts | 6 +- .../test/bridge/EthHelper.test.ts | 54 +-- .../optics-tests/test/bridge/bridge.test.ts | 335 +++++++++--------- .../optics-tests/test/bridge/encoding.test.ts | 1 + typescript/optics-tests/test/common.test.ts | 2 +- .../test/cross-chain/governanceRouter.test.ts | 2 +- .../test/cross-chain/recoveryManager.test.ts | 2 +- .../test/cross-chain/simpleMessage.test.ts | 2 +- .../optics-tests/test/cross-chain/utils.ts | 2 +- typescript/optics-tests/test/home.test.ts | 2 +- typescript/optics-tests/test/replica.test.ts | 2 +- .../test/xAppConnectionManager.test.ts | 2 +- 19 files changed, 604 insertions(+), 408 deletions(-) create mode 100644 typescript/optics-tests/lib/bridge.ts create mode 100644 typescript/optics-tests/lib/core.ts diff --git a/resolve-typechain-conflicts.sh b/resolve-typechain-conflicts.sh index f9f3481c6..f852246eb 100755 --- a/resolve-typechain-conflicts.sh +++ b/resolve-typechain-conflicts.sh @@ -1,5 +1,3 @@ -git rm -rf typescript/typechain/optics-xapps -git rm -rf typescript/typechain/optics-core cd solidity/optics-core npm run compile cd ../optics-xapps diff --git a/typescript/optics-deploy/src/bridge/TestBridgeDeploy.ts b/typescript/optics-deploy/src/bridge/TestBridgeDeploy.ts index 9fbe1641b..74371b211 100644 --- a/typescript/optics-deploy/src/bridge/TestBridgeDeploy.ts +++ b/typescript/optics-deploy/src/bridge/TestBridgeDeploy.ts @@ -1,4 +1,4 @@ -import { BytesLike, ethers, Signer } from 'ethers'; +import { BytesLike, Signer } from 'ethers'; import { UpgradeBeaconController, UpgradeBeaconController__factory, @@ -15,6 +15,7 @@ import { import { ContractVerificationInput } from '../deploy'; import { BridgeContracts } from './BridgeContracts'; import * as process from '.'; +import { TokenId } from '../../../optics-tests/lib/types'; function toBytes32(address: string): string { return '0x' + '00'.repeat(12) + address.slice(2); @@ -127,20 +128,15 @@ export default class TestBridgeDeploy { return 1; } - get remoteDomainBytes(): string { - return `0x0000000${this.remoteDomain}`; - } - - get localDomainBytes(): string { - return `0x0000000${this.localDomain}`; - } - get testToken(): string { return `0x${'11'.repeat(32)}`; } - get testTokenId(): string { - return ethers.utils.hexConcat([this.remoteDomainBytes, this.testToken]); + get testTokenId(): TokenId { + return { + domain: this.remoteDomain, + id: this.testToken + } } async getTestRepresentation(): Promise { diff --git a/typescript/optics-tests/lib/bridge.ts b/typescript/optics-tests/lib/bridge.ts new file mode 100644 index 000000000..95952d0e7 --- /dev/null +++ b/typescript/optics-tests/lib/bridge.ts @@ -0,0 +1,117 @@ +import { assert } from 'chai'; +import { ethers } from 'ethers'; + +import * as types from './types'; + +export enum BridgeMessageTypes { + INVALID = 0, + TOKEN_ID, + MESSAGE, + TRANSFER, + DETAILS, + REQUEST_DETAILS, +} + +const typeToByte = (type: number): string => `0x0${type}`; + +const MESSAGE_LEN = { + identifier: 1, + tokenId: 36, + transfer: 65, + details: 66, + requestDetails: 1 +} + +// Formats Transfer Message +export function formatTransfer(to: ethers.BytesLike, amnt: number | ethers.BytesLike): ethers.BytesLike { + return ethers.utils.solidityPack( + ['bytes1', 'bytes32', 'uint256'], + [BridgeMessageTypes.TRANSFER, to, amnt] + ); +} + +// Formats Details Message +export function formatDetails(name: string, symbol: string, decimals: number): ethers.BytesLike { + return ethers.utils.solidityPack( + ['bytes1', 'bytes32', 'bytes32', 'uint8'], + [BridgeMessageTypes.DETAILS, name, symbol, decimals] + ); +} + +// Formats Request Details message +export function formatRequestDetails(): ethers.BytesLike { + return ethers.utils.solidityPack(['bytes1'], [BridgeMessageTypes.REQUEST_DETAILS]); +} + +// Formats the Token ID +export function formatTokenId(domain: number, id: string): ethers.BytesLike { + return ethers.utils.solidityPack(['uint32', 'bytes32'], [domain, id]); +} + +export function formatMessage(tokenId: ethers.BytesLike, action: ethers.BytesLike): ethers.BytesLike { + return ethers.utils.solidityPack(['bytes', 'bytes'], [tokenId, action]); +} + +export function serializeTransferAction(transferAction: types.TransferAction): ethers.BytesLike { + const { type, recipient, amount } = transferAction; + + assert(type === BridgeMessageTypes.TRANSFER); + return formatTransfer(recipient, amount); +} + +export function serializeDetailsAction(detailsAction: types.DetailsAction): ethers.BytesLike { + const { type, name, symbol, decimal } = detailsAction; + + assert(type === BridgeMessageTypes.DETAILS); + return formatDetails(name, symbol, decimal); +} + +export function serializeRequestDetailsAction(requestDetailsAction: types.RequestDetailsAction): ethers.BytesLike { + assert(requestDetailsAction.type === BridgeMessageTypes.REQUEST_DETAILS); + return formatRequestDetails(); +} + +export function serializeAction(action: types.Action): ethers.BytesLike { + let actionBytes: ethers.BytesLike = []; + switch(action.type) { + case BridgeMessageTypes.TRANSFER: { + actionBytes = serializeTransferAction(action); + break; + } + case BridgeMessageTypes.DETAILS: { + actionBytes = serializeDetailsAction(action); + break; + } + case BridgeMessageTypes.REQUEST_DETAILS: { + actionBytes = serializeRequestDetailsAction(action); + break; + } + default: { + console.error("Invalid action"); + break; + } + } + return actionBytes; +} + +export function serializeTokenId(tokenId: types.TokenId): ethers.BytesLike { + return formatTokenId(tokenId.domain, tokenId.id); +} + +export function serializeMessage(message: types.Message): ethers.BytesLike { + const tokenId = serializeTokenId(message.tokenId); + const action = serializeAction(message.action); + return formatMessage(tokenId, action) +} + +export const bridge: types.HardhatBridgeHelpers = { + BridgeMessageTypes, + typeToByte, + MESSAGE_LEN, + serializeTransferAction, + serializeDetailsAction, + serializeRequestDetailsAction, + serializeAction, + serializeTokenId, + serializeMessage +} \ No newline at end of file diff --git a/typescript/optics-tests/lib/core.ts b/typescript/optics-tests/lib/core.ts new file mode 100644 index 000000000..28218d24e --- /dev/null +++ b/typescript/optics-tests/lib/core.ts @@ -0,0 +1,201 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { assert } from 'chai'; +import * as ethers from 'ethers'; + +import * as types from './types'; +import { getHexStringByteLength } from './utils'; + +export class Updater { + localDomain: types.Domain; + signer: SignerWithAddress; + address: types.Address; + + constructor( + signer: SignerWithAddress, + address: types.Address, + localDomain: types.Domain, + disableWarn: boolean, + ) { + if (!disableWarn) { + throw new Error('Please use `Updater.fromSigner()` to instantiate.'); + } + this.localDomain = localDomain ? localDomain : 0; + this.signer = signer; + this.address = address; + } + + static async fromSigner( + signer: SignerWithAddress, + localDomain: types.Domain, + ) { + return new Updater(signer, await signer.getAddress(), localDomain, true); + } + + domainHash() { + return domainHash(this.localDomain); + } + + message(oldRoot: types.HexString, newRoot: types.HexString) { + return ethers.utils.concat([this.domainHash(), oldRoot, newRoot]); + } + + async signUpdate(oldRoot: types.HexString, newRoot: types.HexString) { + let message = this.message(oldRoot, newRoot); + let msgHash = ethers.utils.arrayify(ethers.utils.keccak256(message)); + let signature = await this.signer.signMessage(msgHash); + return { + origin: this.localDomain, + oldRoot, + newRoot, + signature, + }; + } +} + +const formatMessage = ( + localDomain: types.Domain, + senderAddr: types.Address, + sequence: number, + destinationDomain: types.Domain, + recipientAddr: types.Address, + body: types.HexString, +): string => { + senderAddr = ethersAddressToBytes32(senderAddr); + recipientAddr = ethersAddressToBytes32(recipientAddr); + + return ethers.utils.solidityPack( + ['uint32', 'bytes32', 'uint32', 'uint32', 'bytes32', 'bytes'], + [localDomain, senderAddr, sequence, destinationDomain, recipientAddr, body], + ); +}; + +export enum OpticsState { + UNINITIALIZED = 0, + ACTIVE, + FAILED, +} + +export enum GovernanceMessage { + CALL = 1, + TRANSFERGOVERNOR = 2, + SETROUTER = 3, +} + +export enum MessageStatus { + NONE = 0, + PENDING, + PROCESSED, +} + +function formatTransferGovernor( + newDomain: types.Domain, + newAddress: types.Address, +): string { + return ethers.utils.solidityPack( + ['bytes1', 'uint32', 'bytes32'], + [GovernanceMessage.TRANSFERGOVERNOR, newDomain, newAddress], + ); +} + +function formatSetRouter(domain: types.Domain, address: types.Address): string { + return ethers.utils.solidityPack( + ['bytes1', 'uint32', 'bytes32'], + [GovernanceMessage.SETROUTER, domain, address], + ); +} + +function messageToLeaf(message: types.HexString): string { + return ethers.utils.solidityKeccak256(['bytes'], [message]); +} + +function ethersAddressToBytes32(address: types.Address): string { + return ethers.utils + .hexZeroPad(ethers.utils.hexStripZeros(address), 32) + .toLowerCase(); +} + +function destinationAndSequence( + destination: types.Domain, + sequence: number, +): ethers.BigNumber { + assert(destination < Math.pow(2, 32) - 1); + assert(sequence < Math.pow(2, 32) - 1); + + return ethers.BigNumber.from(destination) + .mul(ethers.BigNumber.from(2).pow(32)) + .add(ethers.BigNumber.from(sequence)); +} + +function domainHash(domain: Number): string { + return ethers.utils.solidityKeccak256( + ['uint32', 'string'], + [domain, 'OPTICS'], + ); +} + +async function signedFailureNotification( + signer: ethers.Signer, + domain: types.Domain, + updaterAddress: types.Address, +): Promise { + const domainCommitment = domainHash(domain); + const updaterBytes32 = ethersAddressToBytes32(updaterAddress); + + const failureNotification = ethers.utils.solidityPack( + ['bytes32', 'uint32', 'bytes32'], + [domainCommitment, domain, updaterBytes32], + ); + const signature = await signer.signMessage( + ethers.utils.arrayify(ethers.utils.keccak256(failureNotification)), + ); + + return { + failureNotification: { + domainCommitment, + domain, + updaterBytes32, + }, + signature, + }; +} + +function formatCalls(callsData: types.CallData[]): string { + let callBody = '0x'; + const numCalls = callsData.length; + + for (let i = 0; i < numCalls; i++) { + const { to, data } = callsData[i]; + const dataLen = getHexStringByteLength(data); + + if (!to || !data) { + throw new Error(`Missing data in Call ${i + 1}: \n ${callsData[i]}`); + } + + let hexBytes = ethers.utils.solidityPack( + ['bytes32', 'uint256', 'bytes'], + [to, dataLen, data], + ); + + // remove 0x before appending + callBody += hexBytes.slice(2); + } + + return ethers.utils.solidityPack( + ['bytes1', 'bytes1', 'bytes'], + [GovernanceMessage.CALL, numCalls, callBody], + ); +} + +export const optics: types.HardhatOpticsHelpers = { + formatMessage, + governance: { + formatTransferGovernor, + formatSetRouter, + formatCalls, + }, + messageToLeaf, + ethersAddressToBytes32, + destinationAndSequence, + domainHash, + signedFailureNotification, +}; \ No newline at end of file diff --git a/typescript/optics-tests/lib/index.ts b/typescript/optics-tests/lib/index.ts index cd928c434..d02ddae39 100644 --- a/typescript/optics-tests/lib/index.ts +++ b/typescript/optics-tests/lib/index.ts @@ -1,205 +1,12 @@ import '@nomiclabs/hardhat-waffle'; -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { assert } from 'chai'; import { extendEnvironment } from 'hardhat/config'; -import * as ethers from 'ethers'; -import * as types from './types'; -import { getHexStringByteLength } from './utils'; -export class Updater { - localDomain: types.Domain; - signer: SignerWithAddress; - address: types.Address; +import { optics } from './core'; - constructor( - signer: SignerWithAddress, - address: types.Address, - localDomain: types.Domain, - disableWarn: boolean, - ) { - if (!disableWarn) { - throw new Error('Please use `Updater.fromSigner()` to instantiate.'); - } - this.localDomain = localDomain ? localDomain : 0; - this.signer = signer; - this.address = address; - } - - static async fromSigner( - signer: SignerWithAddress, - localDomain: types.Domain, - ) { - return new Updater(signer, await signer.getAddress(), localDomain, true); - } - - domainHash() { - return domainHash(this.localDomain); - } - - message(oldRoot: types.HexString, newRoot: types.HexString) { - return ethers.utils.concat([this.domainHash(), oldRoot, newRoot]); - } - - async signUpdate(oldRoot: types.HexString, newRoot: types.HexString) { - let message = this.message(oldRoot, newRoot); - let msgHash = ethers.utils.arrayify(ethers.utils.keccak256(message)); - let signature = await this.signer.signMessage(msgHash); - return { - origin: this.localDomain, - oldRoot, - newRoot, - signature, - }; - } -} - -const formatMessage = ( - localDomain: types.Domain, - senderAddr: types.Address, - sequence: number, - destinationDomain: types.Domain, - recipientAddr: types.Address, - body: types.HexString, -): string => { - senderAddr = ethersAddressToBytes32(senderAddr); - recipientAddr = ethersAddressToBytes32(recipientAddr); - - return ethers.utils.solidityPack( - ['uint32', 'bytes32', 'uint32', 'uint32', 'bytes32', 'bytes'], - [localDomain, senderAddr, sequence, destinationDomain, recipientAddr, body], - ); -}; - -export enum OpticsState { - UNINITIALIZED = 0, - ACTIVE, - FAILED, -} - -export enum GovernanceMessage { - CALL = 1, - TRANSFERGOVERNOR = 2, - SETROUTER = 3, -} - -export enum MessageStatus { - NONE = 0, - PENDING, - PROCESSED, -} - -function formatTransferGovernor( - newDomain: types.Domain, - newAddress: types.Address, -): string { - return ethers.utils.solidityPack( - ['bytes1', 'uint32', 'bytes32'], - [GovernanceMessage.TRANSFERGOVERNOR, newDomain, newAddress], - ); -} - -function formatSetRouter(domain: types.Domain, address: types.Address): string { - return ethers.utils.solidityPack( - ['bytes1', 'uint32', 'bytes32'], - [GovernanceMessage.SETROUTER, domain, address], - ); -} - -function messageToLeaf(message: types.HexString): string { - return ethers.utils.solidityKeccak256(['bytes'], [message]); -} - -function ethersAddressToBytes32(address: types.Address): string { - return ethers.utils - .hexZeroPad(ethers.utils.hexStripZeros(address), 32) - .toLowerCase(); -} - -function destinationAndSequence( - destination: types.Domain, - sequence: number, -): ethers.BigNumber { - assert(destination < Math.pow(2, 32) - 1); - assert(sequence < Math.pow(2, 32) - 1); - - return ethers.BigNumber.from(destination) - .mul(ethers.BigNumber.from(2).pow(32)) - .add(ethers.BigNumber.from(sequence)); -} - -function domainHash(domain: Number): string { - return ethers.utils.solidityKeccak256( - ['uint32', 'string'], - [domain, 'OPTICS'], - ); -} - -async function signedFailureNotification( - signer: ethers.Signer, - domain: types.Domain, - updaterAddress: types.Address, -): Promise { - const domainCommitment = domainHash(domain); - const updaterBytes32 = ethersAddressToBytes32(updaterAddress); - - const failureNotification = ethers.utils.solidityPack( - ['bytes32', 'uint32', 'bytes32'], - [domainCommitment, domain, updaterBytes32], - ); - const signature = await signer.signMessage( - ethers.utils.arrayify(ethers.utils.keccak256(failureNotification)), - ); - - return { - failureNotification: { - domainCommitment, - domain, - updaterBytes32, - }, - signature, - }; -} - -function formatCalls(callsData: types.CallData[]): string { - let callBody = '0x'; - const numCalls = callsData.length; - - for (let i = 0; i < numCalls; i++) { - const { to, data } = callsData[i]; - const dataLen = getHexStringByteLength(data); - - if (!to || !data) { - throw new Error(`Missing data in Call ${i + 1}: \n ${callsData[i]}`); - } - - let hexBytes = ethers.utils.solidityPack( - ['bytes32', 'uint256', 'bytes'], - [to, dataLen, data], - ); - - // remove 0x before appending - callBody += hexBytes.slice(2); - } - - return ethers.utils.solidityPack( - ['bytes1', 'bytes1', 'bytes'], - [GovernanceMessage.CALL, numCalls, callBody], - ); -} +import { bridge } from './bridge'; // HardhatRuntimeEnvironment extendEnvironment((hre) => { - hre.optics = { - formatMessage, - governance: { - formatTransferGovernor, - formatSetRouter, - formatCalls, - }, - messageToLeaf, - ethersAddressToBytes32, - destinationAndSequence, - domainHash, - signedFailureNotification, - }; + hre.optics = optics; + hre.bridge = bridge; }); diff --git a/typescript/optics-tests/lib/permit.ts b/typescript/optics-tests/lib/permit.ts index 6b32058d7..be86d6aa1 100644 --- a/typescript/optics-tests/lib/permit.ts +++ b/typescript/optics-tests/lib/permit.ts @@ -1,4 +1,4 @@ -import { BigNumber, BigNumberish, Bytes, ethers } from 'ethers'; +import { BigNumber, BigNumberish, ethers } from 'ethers'; import { BridgeToken } from '../../typechain/optics-xapps'; const PERMIT_TYPEHASH = ethers.utils.keccak256( @@ -18,7 +18,6 @@ export async function permitDigest( token: BridgeToken, approval: Approval, ): Promise { - const name = await token.name(); const separator = await token.domainSeparator(); const nonce = await token.nonces(approval.owner); diff --git a/typescript/optics-tests/lib/types.ts b/typescript/optics-tests/lib/types.ts index 68eb578fc..2e94be98d 100644 --- a/typescript/optics-tests/lib/types.ts +++ b/typescript/optics-tests/lib/types.ts @@ -1,5 +1,8 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { BytesLike } from 'ethers'; +import { BytesLike, ethers } from 'ethers'; +import { BridgeMessageTypes } from './bridge'; + +/********* HRE *********/ export interface HardhatOpticsHelpers { formatMessage: Function; @@ -15,12 +18,26 @@ export interface HardhatOpticsHelpers { signedFailureNotification: Function; } +export interface HardhatBridgeHelpers { + BridgeMessageTypes: typeof BridgeMessageTypes; + typeToByte: Function; + MESSAGE_LEN: MessageLen; + serializeTransferAction: Function; + serializeDetailsAction: Function; + serializeRequestDetailsAction: Function; + serializeAction: Function; + serializeTokenId: Function; + serializeMessage: Function; +} + declare module 'hardhat/types/runtime' { interface HardhatRuntimeEnvironment { optics: HardhatOpticsHelpers; + bridge: HardhatBridgeHelpers; } } +/********* BASIC TYPES *********/ export type Domain = number; export type Address = string; export type AddressBytes32 = string; @@ -61,6 +78,7 @@ export type BytesArray = [ BytesLike, ]; +/********* OPTICS CORE *********/ export type Update = { oldRoot: string; newRoot: string; @@ -82,3 +100,41 @@ export type SignedFailureNotification = { failureNotification: FailureNotification; signature: string; }; + +/********* TOKEN BRIDGE *********/ + +export type MessageLen = { + identifier: number; + tokenId: number; + transfer: number; + details: number; + requestDetails: number; +} + +export type Action = DetailsAction | TransferAction | RequestDetailsAction; + +export type TokenId = { + domain: number; + id: string; +} +export type Message = { + tokenId: TokenId; + action: Action; +} + +export type TransferAction = { + type: BridgeMessageTypes.TRANSFER; + recipient: ethers.BytesLike; + amount: number | ethers.BytesLike; +} + +export type DetailsAction = { + type: BridgeMessageTypes.DETAILS; + name: string; + symbol: string; + decimal: number; +} + +export type RequestDetailsAction = { + type: BridgeMessageTypes.REQUEST_DETAILS; +} diff --git a/typescript/optics-tests/test/bridge/BridgeToken.test.ts b/typescript/optics-tests/test/bridge/BridgeToken.test.ts index 8c221dca7..5229fb551 100644 --- a/typescript/optics-tests/test/bridge/BridgeToken.test.ts +++ b/typescript/optics-tests/test/bridge/BridgeToken.test.ts @@ -1,13 +1,13 @@ import { ethers } from 'hardhat'; import { expect } from 'chai'; +import { Wallet } from 'ethers'; +import { Signer } from '../../lib/types'; +import { permitDigest } from '../../lib/permit'; import { BridgeToken__factory, BridgeToken, } from '../../../typechain/optics-xapps'; -import { Signer } from '../../lib/types'; -import { permitDigest } from '../../lib/permit'; -import { BigNumber, Wallet } from 'ethers'; const VALUE = 100; diff --git a/typescript/optics-tests/test/bridge/EthHelper.test.ts b/typescript/optics-tests/test/bridge/EthHelper.test.ts index a4c5b1410..6b7db2597 100644 --- a/typescript/optics-tests/test/bridge/EthHelper.test.ts +++ b/typescript/optics-tests/test/bridge/EthHelper.test.ts @@ -1,12 +1,11 @@ -import { ethers } from 'hardhat'; -import { Signer, Wallet } from 'ethers'; -import { - BridgeToken, - BridgeToken__factory, -} from '../../../typechain/optics-xapps'; -import TestBridgeDeploy from '../../../optics-deploy/src/bridge/TestBridgeDeploy'; +import { ethers, bridge } from 'hardhat'; +const { BridgeMessageTypes } = bridge; +import { Signer } from 'ethers'; import { expect } from 'chai'; + +import * as types from '../../lib/types'; import { toBytes32 } from '../../lib/utils'; +import TestBridgeDeploy from '../../../optics-deploy/src/bridge/TestBridgeDeploy'; describe('EthHelper', async () => { let deploy: TestBridgeDeploy; @@ -23,8 +22,6 @@ describe('EthHelper', async () => { let transferMessage: string; const value = 1; - const valueBytes = ethers.utils.zeroPad('0x01', 32); - const TRANSFER_TAG = '0x03'; before(async () => { [deployer, recipient] = await ethers.getSigners(); @@ -34,22 +31,29 @@ describe('EthHelper', async () => { recipientId = toBytes32(recipientAddress).toLowerCase(); deploy = await TestBridgeDeploy.deploy(deployer); - const tokenId = ethers.utils.hexConcat([ - deploy.localDomainBytes, - toBytes32(deploy.mockWeth.address), - ]); - const transferToSelfAction = ethers.utils.hexConcat([ - TRANSFER_TAG, - deployerId, - valueBytes, - ]); - transferToSelfMessage = ethers.utils.hexConcat([tokenId, transferToSelfAction]); - const transferAction = ethers.utils.hexConcat([ - TRANSFER_TAG, - recipientId, - valueBytes, - ]); - transferMessage = ethers.utils.hexConcat([tokenId, transferAction]); + const tokenId: types.TokenId = { + domain: deploy.localDomain, + id: toBytes32(deploy.mockWeth.address) + } + const transferToSelfMessageObj: types.Message = { + tokenId, + action: { + type: BridgeMessageTypes.TRANSFER, + recipient: deployerId, + amount: value + } + } + transferToSelfMessage = bridge.serializeMessage(transferToSelfMessageObj); + + const transferMessageObj: types.Message = { + tokenId, + action: { + type: BridgeMessageTypes.TRANSFER, + recipient: recipientId, + amount: value + } + } + transferMessage = bridge.serializeMessage(transferMessageObj); }); it('send function', async () => { diff --git a/typescript/optics-tests/test/bridge/bridge.test.ts b/typescript/optics-tests/test/bridge/bridge.test.ts index 63fb3afb0..b190cbb14 100644 --- a/typescript/optics-tests/test/bridge/bridge.test.ts +++ b/typescript/optics-tests/test/bridge/bridge.test.ts @@ -1,27 +1,17 @@ -import { ethers } from 'hardhat'; -import { Signer } from '../../lib/types'; +import { expect } from 'chai'; import { BigNumber, BytesLike } from 'ethers'; -import TestBridgeDeploy from '../../../optics-deploy/src/bridge/TestBridgeDeploy'; +import { ethers, bridge } from 'hardhat'; +const { BridgeMessageTypes, typeToByte } = bridge; + +import * as types from '../../lib/types'; import { toBytes32 } from '../../lib/utils'; -import { expect } from 'chai'; +import TestBridgeDeploy from '../../../optics-deploy/src/bridge/TestBridgeDeploy'; import { BridgeToken, BridgeToken__factory, IERC20, } from '../../../typechain/optics-xapps'; -import { assert } from 'console'; -import { domain } from 'process'; - -const BRIDGE_MESSAGE_TYPES = { - INVALID: 0, - TOKEN_ID: 1, - MESSAGE: 2, - TRANSFER: 3, - DETAILS: 4, - REQUEST_DETAILS: 5, -}; -const typeToByte = (type: number): string => `0x0${type}`; const stringToBytes32 = (s: string): string => { const str = Buffer.from(s.slice(0, 32), 'utf-8'); const result = Buffer.alloc(32); @@ -31,20 +21,15 @@ const stringToBytes32 = (s: string): string => { }; describe('BridgeRouter', async () => { - let deployer: Signer; + let deployer: types.Signer; let deployerAddress: string; let deployerId: BytesLike; let deploy: TestBridgeDeploy; const PROTOCOL_PROCESS_GAS = 800_000; - // 1-byte Action Type - const TRANSER_TAG = typeToByte(BRIDGE_MESSAGE_TYPES.TRANSFER); - // Numerical token value const TOKEN_VALUE = 0xffff; - // 32-byte token value - const TOKEN_VALUE_BYTES = `0x${'00'.repeat(30)}ffff`; before(async () => { // populate deployer signer @@ -86,7 +71,6 @@ describe('BridgeRouter', async () => { }); describe('remotely-originating asset roundtrup', async () => { - let transferAction: string; let transferMessage: string; let repr: IERC20; @@ -94,15 +78,15 @@ describe('BridgeRouter', async () => { deploy = await TestBridgeDeploy.deploy(deployer); // generate transfer action - transferAction = ethers.utils.hexConcat([ - TRANSER_TAG, - deployerId, - TOKEN_VALUE_BYTES, - ]); - transferMessage = ethers.utils.hexConcat([ - deploy.testTokenId, - transferAction, - ]); + const transferMessageObj: types.Message = { + tokenId: deploy.testTokenId, + action: { + type: BridgeMessageTypes.TRANSFER, + recipient: deployerId, + amount: TOKEN_VALUE + } + } + transferMessage = bridge.serializeMessage(transferMessageObj); }); it('deploys a token on first inbound transfer', async () => { @@ -117,16 +101,21 @@ describe('BridgeRouter', async () => { expect(representation).to.not.be.undefined; repr = representation!; + const requestDetailsObj: types.Message = { + tokenId: deploy.testTokenId, + action: { + type: BridgeMessageTypes.REQUEST_DETAILS + } + } + const requestDetails = bridge.serializeMessage(requestDetailsObj); + await expect(handleTx).to.emit(deploy.bridgeRouter!, 'TokenDeployed'); await expect(handleTx) .to.emit(deploy.mockCore, 'Enqueue') .withArgs( deploy.remoteDomain, deployerId, - ethers.utils.hexConcat([ - deploy.testTokenId, - typeToByte(BRIDGE_MESSAGE_TYPES.REQUEST_DETAILS), - ]), + requestDetails, ); expect(await repr!.balanceOf(deployer.address)).to.equal( BigNumber.from(TOKEN_VALUE), @@ -200,8 +189,6 @@ describe('BridgeRouter', async () => { }); describe('locally-originating asset roundtrip', async () => { - let localTokenId: string; - let transferAction: string; let transferMessage: string; let localToken: BridgeToken; @@ -213,19 +200,18 @@ describe('BridgeRouter', async () => { await localToken.mint(deployerAddress, TOKEN_VALUE); // generate protocol messages - localTokenId = ethers.utils.hexConcat([ - deploy.localDomainBytes, - toBytes32(localToken.address), - ]); - transferAction = ethers.utils.hexConcat([ - TRANSER_TAG, - deployerId, - TOKEN_VALUE_BYTES, - ]); - transferMessage = ethers.utils.hexConcat([ - localTokenId, - transferAction, - ]); + const transferMessageObj: types.Message = { + tokenId: { + domain: deploy.localDomain, + id: toBytes32(localToken.address) + }, + action: { + type: BridgeMessageTypes.TRANSFER, + recipient: deployerId, + amount: TOKEN_VALUE + } + } + transferMessage = bridge.serializeMessage(transferMessageObj); expect(await localToken.balanceOf(deployerAddress)).to.equal( BigNumber.from(TOKEN_VALUE), @@ -272,6 +258,7 @@ describe('BridgeRouter', async () => { await localToken.balanceOf(deploy.bridgeRouter!.address), ).to.equal(BigNumber.from(0)); }); + it('holds tokens on outbound transfer', async () => { const sendTx = await deploy.bridgeRouter!.send( localToken.address, @@ -288,6 +275,7 @@ describe('BridgeRouter', async () => { await localToken.balanceOf(deploy.bridgeRouter!.address), ).to.equal(BigNumber.from(TOKEN_VALUE)); }); + it('unlocks tokens on inbound transfer', async () => { let handleTx = await deploy.bridgeRouter!.handle( deploy.remoteDomain, @@ -316,15 +304,15 @@ describe('BridgeRouter', async () => { it('errors for non-existing assets', async () => { // generate transfer action - const transferAction = ethers.utils.hexConcat([ - TRANSER_TAG, - deployerId, - TOKEN_VALUE_BYTES, - ]); - const transferMessage = ethers.utils.hexConcat([ - deploy.testTokenId, - transferAction, - ]); + const transferMessageObj: types.Message = { + tokenId: deploy.testTokenId, + action: { + type: BridgeMessageTypes.TRANSFER, + recipient: deployerId, + amount: TOKEN_VALUE + } + } + const transferMessage = bridge.serializeMessage(transferMessageObj); expect(deploy.bridgeRouter!.preFill(transferMessage)).to.be.revertedWith( '!token', @@ -332,12 +320,10 @@ describe('BridgeRouter', async () => { }); describe('remotely-originating asset', async () => { - let setupAction: string; let setupMessage: string; let repr: IERC20; let recipient: string; let recipientId: string; - let transferAction: string; let transferMessage: string; before(async () => { @@ -346,25 +332,28 @@ describe('BridgeRouter', async () => { // generate actions recipient = `0x${'00'.repeat(19)}ff`; recipientId = toBytes32(recipient); - transferAction = ethers.utils.hexConcat([ - TRANSER_TAG, - recipientId, - TOKEN_VALUE_BYTES, - ]); - transferMessage = ethers.utils.hexConcat([ - deploy.testTokenId, - transferAction, - ]); - - setupAction = ethers.utils.hexConcat([ - TRANSER_TAG, - deployerId, - TOKEN_VALUE_BYTES, - ]); - setupMessage = ethers.utils.hexConcat([ - deploy.testTokenId, - setupAction, - ]); + + // transfer message + const transferMessageObj: types.Message = { + tokenId: deploy.testTokenId, + action: { + type: BridgeMessageTypes.TRANSFER, + recipient: recipientId, + amount: TOKEN_VALUE + } + } + transferMessage = bridge.serializeMessage(transferMessageObj); + + // setup message + const setupMessageObj: types.Message = { + tokenId: deploy.testTokenId, + action: { + type: BridgeMessageTypes.TRANSFER, + recipient: deployerId, + amount: TOKEN_VALUE + } + } + setupMessage = bridge.serializeMessage(setupMessageObj); // perform setup const setupTx = await deploy.bridgeRouter!.handle( @@ -417,8 +406,6 @@ describe('BridgeRouter', async () => { let localToken: BridgeToken; let recipient: string; let recipientId: string; - let localTokenId: string; - let transferAction: string; let transferMessage: string; before(async () => { @@ -442,19 +429,19 @@ describe('BridgeRouter', async () => { // generate transfer action recipient = `0x${'00'.repeat(19)}ff`; recipientId = toBytes32(recipient); - localTokenId = ethers.utils.hexConcat([ - deploy.localDomainBytes, - toBytes32(localToken.address), - ]); - transferAction = ethers.utils.hexConcat([ - TRANSER_TAG, - recipientId, - TOKEN_VALUE_BYTES, - ]); - transferMessage = ethers.utils.hexConcat([ - localTokenId, - transferAction, - ]); + + const transferMessageObj: types.Message = { + tokenId: { + domain: deploy.localDomain, + id: toBytes32(localToken.address) + }, + action: { + type: BridgeMessageTypes.TRANSFER, + recipient: recipientId, + amount: TOKEN_VALUE + } + } + transferMessage = bridge.serializeMessage(transferMessageObj); }); it('transfers tokens on prefill', async () => { @@ -500,38 +487,52 @@ describe('BridgeRouter', async () => { await localToken.initialize(); await localToken.setDetails(TEST_NAME, TEST_SYMBOL, TEST_DECIMALS); - requestMessage = ethers.utils.hexConcat([ - deploy.localDomainBytes, - toBytes32(localToken.address), - typeToByte(BRIDGE_MESSAGE_TYPES.REQUEST_DETAILS), - ]); - outgoingDetails = ethers.utils.hexConcat([ - deploy.localDomainBytes, - toBytes32(localToken.address), - typeToByte(BRIDGE_MESSAGE_TYPES.DETAILS), - stringToBytes32(TEST_NAME), - stringToBytes32(TEST_SYMBOL), - [TEST_DECIMALS], - ]); + const requestMessageObj: types.Message = { + tokenId: { + domain: deploy.localDomain, + id: toBytes32(localToken.address) + }, + action: { + type: BridgeMessageTypes.REQUEST_DETAILS + } + } + requestMessage = bridge.serializeMessage(requestMessageObj); + + const outgoingDetailsObj: types.Message = { + tokenId: { + domain: deploy.localDomain, + id: toBytes32(localToken.address) + }, + action: { + type: BridgeMessageTypes.DETAILS, + name: stringToBytes32(TEST_NAME), + symbol: stringToBytes32(TEST_SYMBOL), + decimal: TEST_DECIMALS + } + } + outgoingDetails = bridge.serializeMessage(outgoingDetailsObj); // generate transfer action - const transferAction = ethers.utils.hexConcat([ - TRANSER_TAG, - deployerId, - TOKEN_VALUE_BYTES, - ]); - transferMessage = ethers.utils.hexConcat([ - deploy.testTokenId, - transferAction, - ]); - - incomingDetails = ethers.utils.hexConcat([ - deploy.testTokenId, - typeToByte(BRIDGE_MESSAGE_TYPES.DETAILS), - stringToBytes32(TEST_NAME), - stringToBytes32(TEST_SYMBOL), - [TEST_DECIMALS], - ]); + const transferMessageObj: types.Message = { + tokenId: deploy.testTokenId, + action: { + type: BridgeMessageTypes.TRANSFER, + recipient: deployerId, + amount: TOKEN_VALUE + } + } + transferMessage = bridge.serializeMessage(transferMessageObj); + + const incomingDetailsObj: types.Message = { + tokenId: deploy.testTokenId, + action: { + type: BridgeMessageTypes.DETAILS, + name: stringToBytes32(TEST_NAME), + symbol: stringToBytes32(TEST_SYMBOL), + decimal: TEST_DECIMALS + } + } + incomingDetails = bridge.serializeMessage(incomingDetailsObj); // first send in a transfer to create the repr await deploy.bridgeRouter!.handle( @@ -551,15 +552,20 @@ describe('BridgeRouter', async () => { deploy.testToken, ); + const requestDetailsObj: types.Message = { + tokenId: deploy.testTokenId, + action: { + type: BridgeMessageTypes.REQUEST_DETAILS + } + } + const requestDetails = bridge.serializeMessage(requestDetailsObj); + await expect(requestTx) .to.emit(deploy.mockCore, 'Enqueue') .withArgs( deploy.remoteDomain, deployerId, - ethers.utils.hexConcat([ - deploy.testTokenId, - typeToByte(BRIDGE_MESSAGE_TYPES.REQUEST_DETAILS), - ]), + requestDetails, ); }); @@ -576,11 +582,16 @@ describe('BridgeRouter', async () => { }); it('errors if token is a repr', async () => { - const badRequest = ethers.utils.hexConcat([ - deploy.localDomainBytes, - toBytes32(repr.address), - typeToByte(BRIDGE_MESSAGE_TYPES.REQUEST_DETAILS), - ]); + const badRequestObj: types.Message = { + tokenId: { + domain: deploy.localDomain, + id: toBytes32(repr.address) + }, + action: { + type: BridgeMessageTypes.REQUEST_DETAILS + } + } + const badRequest = bridge.serializeMessage(badRequestObj); let badRequestTx = deploy.bridgeRouter?.handle( deploy.remoteDomain, @@ -592,11 +603,16 @@ describe('BridgeRouter', async () => { }); it('errors if no registered router for response', async () => { - const badRequest = ethers.utils.hexConcat([ - deploy.localDomainBytes, - localToken.address, - typeToByte(BRIDGE_MESSAGE_TYPES.REQUEST_DETAILS), - ]); + const badRequestObj: types.Message = { + tokenId: { + domain: deploy.localDomain, + id: toBytes32(localToken.address) + }, + action: { + type: BridgeMessageTypes.REQUEST_DETAILS + } + } + const badRequest = bridge.serializeMessage(badRequestObj); let badRequestTx = deploy.bridgeRouter?.handle( 3812, @@ -629,20 +645,21 @@ describe('BridgeRouter', async () => { let transferMessage: string; let defaultRepr: BridgeToken; let customRepr: BridgeToken; - const VALUE = `0x${'00'.repeat(24)}ffffffffffffffff`; + const VALUE = `0xffffffffffffffff`; before(async () => { deploy = await TestBridgeDeploy.deploy(deployer); + // generate transfer action - const transferAction = ethers.utils.hexConcat([ - TRANSER_TAG, - deployerId, - VALUE, - ]); - transferMessage = ethers.utils.hexConcat([ - deploy.testTokenId, - transferAction, - ]); + const transferMessageObj: types.Message = { + tokenId: deploy.testTokenId, + action: { + type: BridgeMessageTypes.TRANSFER, + recipient: deployerId, + amount: VALUE + } + } + transferMessage = bridge.serializeMessage(transferMessageObj); // first send in a transfer to create the repr await deploy.bridgeRouter!.handle( @@ -738,15 +755,15 @@ describe('BridgeRouter', async () => { }); it('allows outbound transfers of both assets', async () => { - const smallTransferAction = ethers.utils.hexConcat([ - TRANSER_TAG, - deployerId, - TOKEN_VALUE_BYTES, - ]); - const smallTransferMessage = ethers.utils.hexConcat([ - deploy.testTokenId, - smallTransferAction, - ]); + const smallTransfer: types.Message = { + tokenId: deploy.testTokenId, + action: { + type: BridgeMessageTypes.TRANSFER, + recipient: deployerId, + amount: TOKEN_VALUE + } + } + const smallTransferMessage = bridge.serializeMessage(smallTransfer); const defaultSendTx = await deploy.bridgeRouter!.send( defaultRepr.address, diff --git a/typescript/optics-tests/test/bridge/encoding.test.ts b/typescript/optics-tests/test/bridge/encoding.test.ts index 85a72b59c..2709f2138 100644 --- a/typescript/optics-tests/test/bridge/encoding.test.ts +++ b/typescript/optics-tests/test/bridge/encoding.test.ts @@ -1,4 +1,5 @@ import { ethers } from 'hardhat'; + import * as contracts from '../../../typechain/optics-xapps'; describe('Encoding', async () => { diff --git a/typescript/optics-tests/test/common.test.ts b/typescript/optics-tests/test/common.test.ts index f6225eb55..006981fdc 100644 --- a/typescript/optics-tests/test/common.test.ts +++ b/typescript/optics-tests/test/common.test.ts @@ -2,7 +2,7 @@ import { ethers } from 'hardhat'; import { expect } from 'chai'; import { TestCommon__factory, TestCommon } from '../../typechain/optics-core'; -import { OpticsState, Updater } from '../lib'; +import { OpticsState, Updater } from '../lib/core'; import { Signer } from '../lib/types'; import signedUpdateTestCases from '../../../vectors/signedUpdate.json'; diff --git a/typescript/optics-tests/test/cross-chain/governanceRouter.test.ts b/typescript/optics-tests/test/cross-chain/governanceRouter.test.ts index 74a8533ea..90032b2f6 100644 --- a/typescript/optics-tests/test/cross-chain/governanceRouter.test.ts +++ b/typescript/optics-tests/test/cross-chain/governanceRouter.test.ts @@ -9,7 +9,7 @@ import { } from './utils'; import { increaseTimestampBy, UpgradeTestHelpers } from '../utils'; import { getTestDeploy } from '../testChain'; -import { Updater } from '../../lib'; +import { Updater } from '../../lib/core'; import { Address, Signer } from '../../lib/types'; import { CoreDeploy as Deploy } from '../../../optics-deploy/src/core/CoreDeploy'; import { diff --git a/typescript/optics-tests/test/cross-chain/recoveryManager.test.ts b/typescript/optics-tests/test/cross-chain/recoveryManager.test.ts index 3ac3f98b0..18108a7c3 100644 --- a/typescript/optics-tests/test/cross-chain/recoveryManager.test.ts +++ b/typescript/optics-tests/test/cross-chain/recoveryManager.test.ts @@ -5,7 +5,7 @@ import * as types from 'ethers'; import { formatCall, sendFromSigner } from './utils'; import { increaseTimestampBy } from '../utils'; import { getTestDeploy } from '../testChain'; -import { Updater } from '../../lib'; +import { Updater } from '../../lib/core'; import { Signer } from '../../lib/types'; import { CoreDeploy as Deploy } from '../../../optics-deploy/src/core/CoreDeploy'; import { deployTwoChains } from '../../../optics-deploy/src/core'; diff --git a/typescript/optics-tests/test/cross-chain/simpleMessage.test.ts b/typescript/optics-tests/test/cross-chain/simpleMessage.test.ts index 2a7384594..9a63d26ae 100644 --- a/typescript/optics-tests/test/cross-chain/simpleMessage.test.ts +++ b/typescript/optics-tests/test/cross-chain/simpleMessage.test.ts @@ -4,7 +4,7 @@ import { expect } from 'chai'; import * as utils from './utils'; import { getTestDeploy } from '../testChain'; import { increaseTimestampBy } from '../utils'; -import { Updater, MessageStatus } from '../../lib'; +import { Updater, MessageStatus } from '../../lib/core'; import { Update, Signer, BytesArray } from '../../lib/types'; import { CoreDeploy as Deploy } from '../../../optics-deploy/src/core/CoreDeploy'; import { deployTwoChains } from '../../../optics-deploy/src/core'; diff --git a/typescript/optics-tests/test/cross-chain/utils.ts b/typescript/optics-tests/test/cross-chain/utils.ts index 35c8b3643..5315114ba 100644 --- a/typescript/optics-tests/test/cross-chain/utils.ts +++ b/typescript/optics-tests/test/cross-chain/utils.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { ethers, optics } from 'hardhat'; import * as types from 'ethers'; -import { Updater } from '../../lib'; +import { Updater } from '../../lib/core'; import { Update, CallData, Address } from '../../lib/types'; import { Replica, diff --git a/typescript/optics-tests/test/home.test.ts b/typescript/optics-tests/test/home.test.ts index 1d4a72dc1..6e108a067 100644 --- a/typescript/optics-tests/test/home.test.ts +++ b/typescript/optics-tests/test/home.test.ts @@ -1,7 +1,7 @@ import { ethers, optics } from 'hardhat'; import { expect } from 'chai'; import { getTestDeploy } from './testChain'; -import { OpticsState, Updater } from '../lib'; +import { OpticsState, Updater } from '../lib/core'; import { Signer } from '../lib/types'; import { CoreDeploy as Deploy } from '../../optics-deploy/src/core/CoreDeploy'; import * as deploys from '../../optics-deploy/src/core'; diff --git a/typescript/optics-tests/test/replica.test.ts b/typescript/optics-tests/test/replica.test.ts index a913d3df5..ce35d55f5 100644 --- a/typescript/optics-tests/test/replica.test.ts +++ b/typescript/optics-tests/test/replica.test.ts @@ -3,7 +3,7 @@ import { expect } from 'chai'; import { increaseTimestampBy } from './utils'; import { getTestDeploy } from './testChain'; -import { Updater, OpticsState, MessageStatus } from '../lib'; +import { Updater, OpticsState, MessageStatus } from '../lib/core'; import { Signer, BytesArray } from '../lib/types'; import * as contracts from '../../typechain/optics-core'; import { CoreDeploy as Deploy } from '../../optics-deploy/src/core/CoreDeploy'; diff --git a/typescript/optics-tests/test/xAppConnectionManager.test.ts b/typescript/optics-tests/test/xAppConnectionManager.test.ts index be5b9e2dd..6accc8875 100644 --- a/typescript/optics-tests/test/xAppConnectionManager.test.ts +++ b/typescript/optics-tests/test/xAppConnectionManager.test.ts @@ -2,7 +2,7 @@ import { ethers, optics } from 'hardhat'; import { expect } from 'chai'; import { getTestDeploy } from './testChain'; -import { Updater } from '../lib'; +import { Updater } from '../lib/core'; import { Signer } from '../lib/types'; import { CoreDeploy as Deploy } from '../../optics-deploy/src/core/CoreDeploy'; import * as deploys from '../../optics-deploy/src/core';