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
buddies-main-deployment
Erin Hales 3 years ago committed by GitHub
parent 798396fb90
commit 067817f891
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      resolve-typechain-conflicts.sh
  2. 18
      typescript/optics-deploy/src/bridge/TestBridgeDeploy.ts
  3. 117
      typescript/optics-tests/lib/bridge.ts
  4. 201
      typescript/optics-tests/lib/core.ts
  5. 201
      typescript/optics-tests/lib/index.ts
  6. 3
      typescript/optics-tests/lib/permit.ts
  7. 58
      typescript/optics-tests/lib/types.ts
  8. 6
      typescript/optics-tests/test/bridge/BridgeToken.test.ts
  9. 54
      typescript/optics-tests/test/bridge/EthHelper.test.ts
  10. 335
      typescript/optics-tests/test/bridge/bridge.test.ts
  11. 1
      typescript/optics-tests/test/bridge/encoding.test.ts
  12. 2
      typescript/optics-tests/test/common.test.ts
  13. 2
      typescript/optics-tests/test/cross-chain/governanceRouter.test.ts
  14. 2
      typescript/optics-tests/test/cross-chain/recoveryManager.test.ts
  15. 2
      typescript/optics-tests/test/cross-chain/simpleMessage.test.ts
  16. 2
      typescript/optics-tests/test/cross-chain/utils.ts
  17. 2
      typescript/optics-tests/test/home.test.ts
  18. 2
      typescript/optics-tests/test/replica.test.ts
  19. 2
      typescript/optics-tests/test/xAppConnectionManager.test.ts

@ -1,5 +1,3 @@
git rm -rf typescript/typechain/optics-xapps
git rm -rf typescript/typechain/optics-core
cd solidity/optics-core cd solidity/optics-core
npm run compile npm run compile
cd ../optics-xapps cd ../optics-xapps

@ -1,4 +1,4 @@
import { BytesLike, ethers, Signer } from 'ethers'; import { BytesLike, Signer } from 'ethers';
import { import {
UpgradeBeaconController, UpgradeBeaconController,
UpgradeBeaconController__factory, UpgradeBeaconController__factory,
@ -15,6 +15,7 @@ import {
import { ContractVerificationInput } from '../deploy'; import { ContractVerificationInput } from '../deploy';
import { BridgeContracts } from './BridgeContracts'; import { BridgeContracts } from './BridgeContracts';
import * as process from '.'; import * as process from '.';
import { TokenId } from '../../../optics-tests/lib/types';
function toBytes32(address: string): string { function toBytes32(address: string): string {
return '0x' + '00'.repeat(12) + address.slice(2); return '0x' + '00'.repeat(12) + address.slice(2);
@ -127,20 +128,15 @@ export default class TestBridgeDeploy {
return 1; return 1;
} }
get remoteDomainBytes(): string {
return `0x0000000${this.remoteDomain}`;
}
get localDomainBytes(): string {
return `0x0000000${this.localDomain}`;
}
get testToken(): string { get testToken(): string {
return `0x${'11'.repeat(32)}`; return `0x${'11'.repeat(32)}`;
} }
get testTokenId(): string { get testTokenId(): TokenId {
return ethers.utils.hexConcat([this.remoteDomainBytes, this.testToken]); return {
domain: this.remoteDomain,
id: this.testToken
}
} }
async getTestRepresentation(): Promise<BridgeToken | undefined> { async getTestRepresentation(): Promise<BridgeToken | undefined> {

@ -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
}

@ -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<types.SignedFailureNotification> {
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,
};

@ -1,205 +1,12 @@
import '@nomiclabs/hardhat-waffle'; import '@nomiclabs/hardhat-waffle';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { assert } from 'chai';
import { extendEnvironment } from 'hardhat/config'; import { extendEnvironment } from 'hardhat/config';
import * as ethers from 'ethers';
import * as types from './types';
import { getHexStringByteLength } from './utils';
export class Updater { import { optics } from './core';
localDomain: types.Domain;
signer: SignerWithAddress;
address: types.Address;
constructor( import { bridge } from './bridge';
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<types.SignedFailureNotification> {
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],
);
}
// HardhatRuntimeEnvironment // HardhatRuntimeEnvironment
extendEnvironment((hre) => { extendEnvironment((hre) => {
hre.optics = { hre.optics = optics;
formatMessage, hre.bridge = bridge;
governance: {
formatTransferGovernor,
formatSetRouter,
formatCalls,
},
messageToLeaf,
ethersAddressToBytes32,
destinationAndSequence,
domainHash,
signedFailureNotification,
};
}); });

@ -1,4 +1,4 @@
import { BigNumber, BigNumberish, Bytes, ethers } from 'ethers'; import { BigNumber, BigNumberish, ethers } from 'ethers';
import { BridgeToken } from '../../typechain/optics-xapps'; import { BridgeToken } from '../../typechain/optics-xapps';
const PERMIT_TYPEHASH = ethers.utils.keccak256( const PERMIT_TYPEHASH = ethers.utils.keccak256(
@ -18,7 +18,6 @@ export async function permitDigest(
token: BridgeToken, token: BridgeToken,
approval: Approval, approval: Approval,
): Promise<string> { ): Promise<string> {
const name = await token.name();
const separator = await token.domainSeparator(); const separator = await token.domainSeparator();
const nonce = await token.nonces(approval.owner); const nonce = await token.nonces(approval.owner);

@ -1,5 +1,8 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { BytesLike } from 'ethers'; import { BytesLike, ethers } from 'ethers';
import { BridgeMessageTypes } from './bridge';
/********* HRE *********/
export interface HardhatOpticsHelpers { export interface HardhatOpticsHelpers {
formatMessage: Function; formatMessage: Function;
@ -15,12 +18,26 @@ export interface HardhatOpticsHelpers {
signedFailureNotification: Function; 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' { declare module 'hardhat/types/runtime' {
interface HardhatRuntimeEnvironment { interface HardhatRuntimeEnvironment {
optics: HardhatOpticsHelpers; optics: HardhatOpticsHelpers;
bridge: HardhatBridgeHelpers;
} }
} }
/********* BASIC TYPES *********/
export type Domain = number; export type Domain = number;
export type Address = string; export type Address = string;
export type AddressBytes32 = string; export type AddressBytes32 = string;
@ -61,6 +78,7 @@ export type BytesArray = [
BytesLike, BytesLike,
]; ];
/********* OPTICS CORE *********/
export type Update = { export type Update = {
oldRoot: string; oldRoot: string;
newRoot: string; newRoot: string;
@ -82,3 +100,41 @@ export type SignedFailureNotification = {
failureNotification: FailureNotification; failureNotification: FailureNotification;
signature: string; 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;
}

@ -1,13 +1,13 @@
import { ethers } from 'hardhat'; import { ethers } from 'hardhat';
import { expect } from 'chai'; import { expect } from 'chai';
import { Wallet } from 'ethers';
import { Signer } from '../../lib/types';
import { permitDigest } from '../../lib/permit';
import { import {
BridgeToken__factory, BridgeToken__factory,
BridgeToken, BridgeToken,
} from '../../../typechain/optics-xapps'; } from '../../../typechain/optics-xapps';
import { Signer } from '../../lib/types';
import { permitDigest } from '../../lib/permit';
import { BigNumber, Wallet } from 'ethers';
const VALUE = 100; const VALUE = 100;

@ -1,12 +1,11 @@
import { ethers } from 'hardhat'; import { ethers, bridge } from 'hardhat';
import { Signer, Wallet } from 'ethers'; const { BridgeMessageTypes } = bridge;
import { import { Signer } from 'ethers';
BridgeToken,
BridgeToken__factory,
} from '../../../typechain/optics-xapps';
import TestBridgeDeploy from '../../../optics-deploy/src/bridge/TestBridgeDeploy';
import { expect } from 'chai'; import { expect } from 'chai';
import * as types from '../../lib/types';
import { toBytes32 } from '../../lib/utils'; import { toBytes32 } from '../../lib/utils';
import TestBridgeDeploy from '../../../optics-deploy/src/bridge/TestBridgeDeploy';
describe('EthHelper', async () => { describe('EthHelper', async () => {
let deploy: TestBridgeDeploy; let deploy: TestBridgeDeploy;
@ -23,8 +22,6 @@ describe('EthHelper', async () => {
let transferMessage: string; let transferMessage: string;
const value = 1; const value = 1;
const valueBytes = ethers.utils.zeroPad('0x01', 32);
const TRANSFER_TAG = '0x03';
before(async () => { before(async () => {
[deployer, recipient] = await ethers.getSigners(); [deployer, recipient] = await ethers.getSigners();
@ -34,22 +31,29 @@ describe('EthHelper', async () => {
recipientId = toBytes32(recipientAddress).toLowerCase(); recipientId = toBytes32(recipientAddress).toLowerCase();
deploy = await TestBridgeDeploy.deploy(deployer); deploy = await TestBridgeDeploy.deploy(deployer);
const tokenId = ethers.utils.hexConcat([ const tokenId: types.TokenId = {
deploy.localDomainBytes, domain: deploy.localDomain,
toBytes32(deploy.mockWeth.address), id: toBytes32(deploy.mockWeth.address)
]); }
const transferToSelfAction = ethers.utils.hexConcat([ const transferToSelfMessageObj: types.Message = {
TRANSFER_TAG, tokenId,
deployerId, action: {
valueBytes, type: BridgeMessageTypes.TRANSFER,
]); recipient: deployerId,
transferToSelfMessage = ethers.utils.hexConcat([tokenId, transferToSelfAction]); amount: value
const transferAction = ethers.utils.hexConcat([ }
TRANSFER_TAG, }
recipientId, transferToSelfMessage = bridge.serializeMessage(transferToSelfMessageObj);
valueBytes,
]); const transferMessageObj: types.Message = {
transferMessage = ethers.utils.hexConcat([tokenId, transferAction]); tokenId,
action: {
type: BridgeMessageTypes.TRANSFER,
recipient: recipientId,
amount: value
}
}
transferMessage = bridge.serializeMessage(transferMessageObj);
}); });
it('send function', async () => { it('send function', async () => {

@ -1,27 +1,17 @@
import { ethers } from 'hardhat'; import { expect } from 'chai';
import { Signer } from '../../lib/types';
import { BigNumber, BytesLike } from 'ethers'; 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 { toBytes32 } from '../../lib/utils';
import { expect } from 'chai'; import TestBridgeDeploy from '../../../optics-deploy/src/bridge/TestBridgeDeploy';
import { import {
BridgeToken, BridgeToken,
BridgeToken__factory, BridgeToken__factory,
IERC20, IERC20,
} from '../../../typechain/optics-xapps'; } 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 stringToBytes32 = (s: string): string => {
const str = Buffer.from(s.slice(0, 32), 'utf-8'); const str = Buffer.from(s.slice(0, 32), 'utf-8');
const result = Buffer.alloc(32); const result = Buffer.alloc(32);
@ -31,20 +21,15 @@ const stringToBytes32 = (s: string): string => {
}; };
describe('BridgeRouter', async () => { describe('BridgeRouter', async () => {
let deployer: Signer; let deployer: types.Signer;
let deployerAddress: string; let deployerAddress: string;
let deployerId: BytesLike; let deployerId: BytesLike;
let deploy: TestBridgeDeploy; let deploy: TestBridgeDeploy;
const PROTOCOL_PROCESS_GAS = 800_000; const PROTOCOL_PROCESS_GAS = 800_000;
// 1-byte Action Type
const TRANSER_TAG = typeToByte(BRIDGE_MESSAGE_TYPES.TRANSFER);
// Numerical token value // Numerical token value
const TOKEN_VALUE = 0xffff; const TOKEN_VALUE = 0xffff;
// 32-byte token value
const TOKEN_VALUE_BYTES = `0x${'00'.repeat(30)}ffff`;
before(async () => { before(async () => {
// populate deployer signer // populate deployer signer
@ -86,7 +71,6 @@ describe('BridgeRouter', async () => {
}); });
describe('remotely-originating asset roundtrup', async () => { describe('remotely-originating asset roundtrup', async () => {
let transferAction: string;
let transferMessage: string; let transferMessage: string;
let repr: IERC20; let repr: IERC20;
@ -94,15 +78,15 @@ describe('BridgeRouter', async () => {
deploy = await TestBridgeDeploy.deploy(deployer); deploy = await TestBridgeDeploy.deploy(deployer);
// generate transfer action // generate transfer action
transferAction = ethers.utils.hexConcat([ const transferMessageObj: types.Message = {
TRANSER_TAG, tokenId: deploy.testTokenId,
deployerId, action: {
TOKEN_VALUE_BYTES, type: BridgeMessageTypes.TRANSFER,
]); recipient: deployerId,
transferMessage = ethers.utils.hexConcat([ amount: TOKEN_VALUE
deploy.testTokenId, }
transferAction, }
]); transferMessage = bridge.serializeMessage(transferMessageObj);
}); });
it('deploys a token on first inbound transfer', async () => { it('deploys a token on first inbound transfer', async () => {
@ -117,16 +101,21 @@ describe('BridgeRouter', async () => {
expect(representation).to.not.be.undefined; expect(representation).to.not.be.undefined;
repr = representation!; 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.bridgeRouter!, 'TokenDeployed');
await expect(handleTx) await expect(handleTx)
.to.emit(deploy.mockCore, 'Enqueue') .to.emit(deploy.mockCore, 'Enqueue')
.withArgs( .withArgs(
deploy.remoteDomain, deploy.remoteDomain,
deployerId, deployerId,
ethers.utils.hexConcat([ requestDetails,
deploy.testTokenId,
typeToByte(BRIDGE_MESSAGE_TYPES.REQUEST_DETAILS),
]),
); );
expect(await repr!.balanceOf(deployer.address)).to.equal( expect(await repr!.balanceOf(deployer.address)).to.equal(
BigNumber.from(TOKEN_VALUE), BigNumber.from(TOKEN_VALUE),
@ -200,8 +189,6 @@ describe('BridgeRouter', async () => {
}); });
describe('locally-originating asset roundtrip', async () => { describe('locally-originating asset roundtrip', async () => {
let localTokenId: string;
let transferAction: string;
let transferMessage: string; let transferMessage: string;
let localToken: BridgeToken; let localToken: BridgeToken;
@ -213,19 +200,18 @@ describe('BridgeRouter', async () => {
await localToken.mint(deployerAddress, TOKEN_VALUE); await localToken.mint(deployerAddress, TOKEN_VALUE);
// generate protocol messages // generate protocol messages
localTokenId = ethers.utils.hexConcat([ const transferMessageObj: types.Message = {
deploy.localDomainBytes, tokenId: {
toBytes32(localToken.address), domain: deploy.localDomain,
]); id: toBytes32(localToken.address)
transferAction = ethers.utils.hexConcat([ },
TRANSER_TAG, action: {
deployerId, type: BridgeMessageTypes.TRANSFER,
TOKEN_VALUE_BYTES, recipient: deployerId,
]); amount: TOKEN_VALUE
transferMessage = ethers.utils.hexConcat([ }
localTokenId, }
transferAction, transferMessage = bridge.serializeMessage(transferMessageObj);
]);
expect(await localToken.balanceOf(deployerAddress)).to.equal( expect(await localToken.balanceOf(deployerAddress)).to.equal(
BigNumber.from(TOKEN_VALUE), BigNumber.from(TOKEN_VALUE),
@ -272,6 +258,7 @@ describe('BridgeRouter', async () => {
await localToken.balanceOf(deploy.bridgeRouter!.address), await localToken.balanceOf(deploy.bridgeRouter!.address),
).to.equal(BigNumber.from(0)); ).to.equal(BigNumber.from(0));
}); });
it('holds tokens on outbound transfer', async () => { it('holds tokens on outbound transfer', async () => {
const sendTx = await deploy.bridgeRouter!.send( const sendTx = await deploy.bridgeRouter!.send(
localToken.address, localToken.address,
@ -288,6 +275,7 @@ describe('BridgeRouter', async () => {
await localToken.balanceOf(deploy.bridgeRouter!.address), await localToken.balanceOf(deploy.bridgeRouter!.address),
).to.equal(BigNumber.from(TOKEN_VALUE)); ).to.equal(BigNumber.from(TOKEN_VALUE));
}); });
it('unlocks tokens on inbound transfer', async () => { it('unlocks tokens on inbound transfer', async () => {
let handleTx = await deploy.bridgeRouter!.handle( let handleTx = await deploy.bridgeRouter!.handle(
deploy.remoteDomain, deploy.remoteDomain,
@ -316,15 +304,15 @@ describe('BridgeRouter', async () => {
it('errors for non-existing assets', async () => { it('errors for non-existing assets', async () => {
// generate transfer action // generate transfer action
const transferAction = ethers.utils.hexConcat([ const transferMessageObj: types.Message = {
TRANSER_TAG, tokenId: deploy.testTokenId,
deployerId, action: {
TOKEN_VALUE_BYTES, type: BridgeMessageTypes.TRANSFER,
]); recipient: deployerId,
const transferMessage = ethers.utils.hexConcat([ amount: TOKEN_VALUE
deploy.testTokenId, }
transferAction, }
]); const transferMessage = bridge.serializeMessage(transferMessageObj);
expect(deploy.bridgeRouter!.preFill(transferMessage)).to.be.revertedWith( expect(deploy.bridgeRouter!.preFill(transferMessage)).to.be.revertedWith(
'!token', '!token',
@ -332,12 +320,10 @@ describe('BridgeRouter', async () => {
}); });
describe('remotely-originating asset', async () => { describe('remotely-originating asset', async () => {
let setupAction: string;
let setupMessage: string; let setupMessage: string;
let repr: IERC20; let repr: IERC20;
let recipient: string; let recipient: string;
let recipientId: string; let recipientId: string;
let transferAction: string;
let transferMessage: string; let transferMessage: string;
before(async () => { before(async () => {
@ -346,25 +332,28 @@ describe('BridgeRouter', async () => {
// generate actions // generate actions
recipient = `0x${'00'.repeat(19)}ff`; recipient = `0x${'00'.repeat(19)}ff`;
recipientId = toBytes32(recipient); recipientId = toBytes32(recipient);
transferAction = ethers.utils.hexConcat([
TRANSER_TAG, // transfer message
recipientId, const transferMessageObj: types.Message = {
TOKEN_VALUE_BYTES, tokenId: deploy.testTokenId,
]); action: {
transferMessage = ethers.utils.hexConcat([ type: BridgeMessageTypes.TRANSFER,
deploy.testTokenId, recipient: recipientId,
transferAction, amount: TOKEN_VALUE
]); }
}
setupAction = ethers.utils.hexConcat([ transferMessage = bridge.serializeMessage(transferMessageObj);
TRANSER_TAG,
deployerId, // setup message
TOKEN_VALUE_BYTES, const setupMessageObj: types.Message = {
]); tokenId: deploy.testTokenId,
setupMessage = ethers.utils.hexConcat([ action: {
deploy.testTokenId, type: BridgeMessageTypes.TRANSFER,
setupAction, recipient: deployerId,
]); amount: TOKEN_VALUE
}
}
setupMessage = bridge.serializeMessage(setupMessageObj);
// perform setup // perform setup
const setupTx = await deploy.bridgeRouter!.handle( const setupTx = await deploy.bridgeRouter!.handle(
@ -417,8 +406,6 @@ describe('BridgeRouter', async () => {
let localToken: BridgeToken; let localToken: BridgeToken;
let recipient: string; let recipient: string;
let recipientId: string; let recipientId: string;
let localTokenId: string;
let transferAction: string;
let transferMessage: string; let transferMessage: string;
before(async () => { before(async () => {
@ -442,19 +429,19 @@ describe('BridgeRouter', async () => {
// generate transfer action // generate transfer action
recipient = `0x${'00'.repeat(19)}ff`; recipient = `0x${'00'.repeat(19)}ff`;
recipientId = toBytes32(recipient); recipientId = toBytes32(recipient);
localTokenId = ethers.utils.hexConcat([
deploy.localDomainBytes, const transferMessageObj: types.Message = {
toBytes32(localToken.address), tokenId: {
]); domain: deploy.localDomain,
transferAction = ethers.utils.hexConcat([ id: toBytes32(localToken.address)
TRANSER_TAG, },
recipientId, action: {
TOKEN_VALUE_BYTES, type: BridgeMessageTypes.TRANSFER,
]); recipient: recipientId,
transferMessage = ethers.utils.hexConcat([ amount: TOKEN_VALUE
localTokenId, }
transferAction, }
]); transferMessage = bridge.serializeMessage(transferMessageObj);
}); });
it('transfers tokens on prefill', async () => { it('transfers tokens on prefill', async () => {
@ -500,38 +487,52 @@ describe('BridgeRouter', async () => {
await localToken.initialize(); await localToken.initialize();
await localToken.setDetails(TEST_NAME, TEST_SYMBOL, TEST_DECIMALS); await localToken.setDetails(TEST_NAME, TEST_SYMBOL, TEST_DECIMALS);
requestMessage = ethers.utils.hexConcat([ const requestMessageObj: types.Message = {
deploy.localDomainBytes, tokenId: {
toBytes32(localToken.address), domain: deploy.localDomain,
typeToByte(BRIDGE_MESSAGE_TYPES.REQUEST_DETAILS), id: toBytes32(localToken.address)
]); },
outgoingDetails = ethers.utils.hexConcat([ action: {
deploy.localDomainBytes, type: BridgeMessageTypes.REQUEST_DETAILS
toBytes32(localToken.address), }
typeToByte(BRIDGE_MESSAGE_TYPES.DETAILS), }
stringToBytes32(TEST_NAME), requestMessage = bridge.serializeMessage(requestMessageObj);
stringToBytes32(TEST_SYMBOL),
[TEST_DECIMALS], 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 // generate transfer action
const transferAction = ethers.utils.hexConcat([ const transferMessageObj: types.Message = {
TRANSER_TAG, tokenId: deploy.testTokenId,
deployerId, action: {
TOKEN_VALUE_BYTES, type: BridgeMessageTypes.TRANSFER,
]); recipient: deployerId,
transferMessage = ethers.utils.hexConcat([ amount: TOKEN_VALUE
deploy.testTokenId, }
transferAction, }
]); transferMessage = bridge.serializeMessage(transferMessageObj);
incomingDetails = ethers.utils.hexConcat([ const incomingDetailsObj: types.Message = {
deploy.testTokenId, tokenId: deploy.testTokenId,
typeToByte(BRIDGE_MESSAGE_TYPES.DETAILS), action: {
stringToBytes32(TEST_NAME), type: BridgeMessageTypes.DETAILS,
stringToBytes32(TEST_SYMBOL), name: stringToBytes32(TEST_NAME),
[TEST_DECIMALS], symbol: stringToBytes32(TEST_SYMBOL),
]); decimal: TEST_DECIMALS
}
}
incomingDetails = bridge.serializeMessage(incomingDetailsObj);
// first send in a transfer to create the repr // first send in a transfer to create the repr
await deploy.bridgeRouter!.handle( await deploy.bridgeRouter!.handle(
@ -551,15 +552,20 @@ describe('BridgeRouter', async () => {
deploy.testToken, deploy.testToken,
); );
const requestDetailsObj: types.Message = {
tokenId: deploy.testTokenId,
action: {
type: BridgeMessageTypes.REQUEST_DETAILS
}
}
const requestDetails = bridge.serializeMessage(requestDetailsObj);
await expect(requestTx) await expect(requestTx)
.to.emit(deploy.mockCore, 'Enqueue') .to.emit(deploy.mockCore, 'Enqueue')
.withArgs( .withArgs(
deploy.remoteDomain, deploy.remoteDomain,
deployerId, deployerId,
ethers.utils.hexConcat([ requestDetails,
deploy.testTokenId,
typeToByte(BRIDGE_MESSAGE_TYPES.REQUEST_DETAILS),
]),
); );
}); });
@ -576,11 +582,16 @@ describe('BridgeRouter', async () => {
}); });
it('errors if token is a repr', async () => { it('errors if token is a repr', async () => {
const badRequest = ethers.utils.hexConcat([ const badRequestObj: types.Message = {
deploy.localDomainBytes, tokenId: {
toBytes32(repr.address), domain: deploy.localDomain,
typeToByte(BRIDGE_MESSAGE_TYPES.REQUEST_DETAILS), id: toBytes32(repr.address)
]); },
action: {
type: BridgeMessageTypes.REQUEST_DETAILS
}
}
const badRequest = bridge.serializeMessage(badRequestObj);
let badRequestTx = deploy.bridgeRouter?.handle( let badRequestTx = deploy.bridgeRouter?.handle(
deploy.remoteDomain, deploy.remoteDomain,
@ -592,11 +603,16 @@ describe('BridgeRouter', async () => {
}); });
it('errors if no registered router for response', async () => { it('errors if no registered router for response', async () => {
const badRequest = ethers.utils.hexConcat([ const badRequestObj: types.Message = {
deploy.localDomainBytes, tokenId: {
localToken.address, domain: deploy.localDomain,
typeToByte(BRIDGE_MESSAGE_TYPES.REQUEST_DETAILS), id: toBytes32(localToken.address)
]); },
action: {
type: BridgeMessageTypes.REQUEST_DETAILS
}
}
const badRequest = bridge.serializeMessage(badRequestObj);
let badRequestTx = deploy.bridgeRouter?.handle( let badRequestTx = deploy.bridgeRouter?.handle(
3812, 3812,
@ -629,20 +645,21 @@ describe('BridgeRouter', async () => {
let transferMessage: string; let transferMessage: string;
let defaultRepr: BridgeToken; let defaultRepr: BridgeToken;
let customRepr: BridgeToken; let customRepr: BridgeToken;
const VALUE = `0x${'00'.repeat(24)}ffffffffffffffff`; const VALUE = `0xffffffffffffffff`;
before(async () => { before(async () => {
deploy = await TestBridgeDeploy.deploy(deployer); deploy = await TestBridgeDeploy.deploy(deployer);
// generate transfer action // generate transfer action
const transferAction = ethers.utils.hexConcat([ const transferMessageObj: types.Message = {
TRANSER_TAG, tokenId: deploy.testTokenId,
deployerId, action: {
VALUE, type: BridgeMessageTypes.TRANSFER,
]); recipient: deployerId,
transferMessage = ethers.utils.hexConcat([ amount: VALUE
deploy.testTokenId, }
transferAction, }
]); transferMessage = bridge.serializeMessage(transferMessageObj);
// first send in a transfer to create the repr // first send in a transfer to create the repr
await deploy.bridgeRouter!.handle( await deploy.bridgeRouter!.handle(
@ -738,15 +755,15 @@ describe('BridgeRouter', async () => {
}); });
it('allows outbound transfers of both assets', async () => { it('allows outbound transfers of both assets', async () => {
const smallTransferAction = ethers.utils.hexConcat([ const smallTransfer: types.Message = {
TRANSER_TAG, tokenId: deploy.testTokenId,
deployerId, action: {
TOKEN_VALUE_BYTES, type: BridgeMessageTypes.TRANSFER,
]); recipient: deployerId,
const smallTransferMessage = ethers.utils.hexConcat([ amount: TOKEN_VALUE
deploy.testTokenId, }
smallTransferAction, }
]); const smallTransferMessage = bridge.serializeMessage(smallTransfer);
const defaultSendTx = await deploy.bridgeRouter!.send( const defaultSendTx = await deploy.bridgeRouter!.send(
defaultRepr.address, defaultRepr.address,

@ -1,4 +1,5 @@
import { ethers } from 'hardhat'; import { ethers } from 'hardhat';
import * as contracts from '../../../typechain/optics-xapps'; import * as contracts from '../../../typechain/optics-xapps';
describe('Encoding', async () => { describe('Encoding', async () => {

@ -2,7 +2,7 @@ import { ethers } from 'hardhat';
import { expect } from 'chai'; import { expect } from 'chai';
import { TestCommon__factory, TestCommon } from '../../typechain/optics-core'; 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 { Signer } from '../lib/types';
import signedUpdateTestCases from '../../../vectors/signedUpdate.json'; import signedUpdateTestCases from '../../../vectors/signedUpdate.json';

@ -9,7 +9,7 @@ import {
} from './utils'; } from './utils';
import { increaseTimestampBy, UpgradeTestHelpers } from '../utils'; import { increaseTimestampBy, UpgradeTestHelpers } from '../utils';
import { getTestDeploy } from '../testChain'; import { getTestDeploy } from '../testChain';
import { Updater } from '../../lib'; import { Updater } from '../../lib/core';
import { Address, Signer } from '../../lib/types'; import { Address, Signer } from '../../lib/types';
import { CoreDeploy as Deploy } from '../../../optics-deploy/src/core/CoreDeploy'; import { CoreDeploy as Deploy } from '../../../optics-deploy/src/core/CoreDeploy';
import { import {

@ -5,7 +5,7 @@ import * as types from 'ethers';
import { formatCall, sendFromSigner } from './utils'; import { formatCall, sendFromSigner } from './utils';
import { increaseTimestampBy } from '../utils'; import { increaseTimestampBy } from '../utils';
import { getTestDeploy } from '../testChain'; import { getTestDeploy } from '../testChain';
import { Updater } from '../../lib'; import { Updater } from '../../lib/core';
import { Signer } from '../../lib/types'; import { Signer } from '../../lib/types';
import { CoreDeploy as Deploy } from '../../../optics-deploy/src/core/CoreDeploy'; import { CoreDeploy as Deploy } from '../../../optics-deploy/src/core/CoreDeploy';
import { deployTwoChains } from '../../../optics-deploy/src/core'; import { deployTwoChains } from '../../../optics-deploy/src/core';

@ -4,7 +4,7 @@ import { expect } from 'chai';
import * as utils from './utils'; import * as utils from './utils';
import { getTestDeploy } from '../testChain'; import { getTestDeploy } from '../testChain';
import { increaseTimestampBy } from '../utils'; import { increaseTimestampBy } from '../utils';
import { Updater, MessageStatus } from '../../lib'; import { Updater, MessageStatus } from '../../lib/core';
import { Update, Signer, BytesArray } from '../../lib/types'; import { Update, Signer, BytesArray } from '../../lib/types';
import { CoreDeploy as Deploy } from '../../../optics-deploy/src/core/CoreDeploy'; import { CoreDeploy as Deploy } from '../../../optics-deploy/src/core/CoreDeploy';
import { deployTwoChains } from '../../../optics-deploy/src/core'; import { deployTwoChains } from '../../../optics-deploy/src/core';

@ -2,7 +2,7 @@ import { expect } from 'chai';
import { ethers, optics } from 'hardhat'; import { ethers, optics } from 'hardhat';
import * as types from 'ethers'; import * as types from 'ethers';
import { Updater } from '../../lib'; import { Updater } from '../../lib/core';
import { Update, CallData, Address } from '../../lib/types'; import { Update, CallData, Address } from '../../lib/types';
import { import {
Replica, Replica,

@ -1,7 +1,7 @@
import { ethers, optics } from 'hardhat'; import { ethers, optics } from 'hardhat';
import { expect } from 'chai'; import { expect } from 'chai';
import { getTestDeploy } from './testChain'; import { getTestDeploy } from './testChain';
import { OpticsState, Updater } from '../lib'; import { OpticsState, Updater } from '../lib/core';
import { Signer } from '../lib/types'; import { Signer } from '../lib/types';
import { CoreDeploy as Deploy } from '../../optics-deploy/src/core/CoreDeploy'; import { CoreDeploy as Deploy } from '../../optics-deploy/src/core/CoreDeploy';
import * as deploys from '../../optics-deploy/src/core'; import * as deploys from '../../optics-deploy/src/core';

@ -3,7 +3,7 @@ import { expect } from 'chai';
import { increaseTimestampBy } from './utils'; import { increaseTimestampBy } from './utils';
import { getTestDeploy } from './testChain'; import { getTestDeploy } from './testChain';
import { Updater, OpticsState, MessageStatus } from '../lib'; import { Updater, OpticsState, MessageStatus } from '../lib/core';
import { Signer, BytesArray } from '../lib/types'; import { Signer, BytesArray } from '../lib/types';
import * as contracts from '../../typechain/optics-core'; import * as contracts from '../../typechain/optics-core';
import { CoreDeploy as Deploy } from '../../optics-deploy/src/core/CoreDeploy'; import { CoreDeploy as Deploy } from '../../optics-deploy/src/core/CoreDeploy';

@ -2,7 +2,7 @@ import { ethers, optics } from 'hardhat';
import { expect } from 'chai'; import { expect } from 'chai';
import { getTestDeploy } from './testChain'; import { getTestDeploy } from './testChain';
import { Updater } from '../lib'; import { Updater } from '../lib/core';
import { Signer } from '../lib/types'; import { Signer } from '../lib/types';
import { CoreDeploy as Deploy } from '../../optics-deploy/src/core/CoreDeploy'; import { CoreDeploy as Deploy } from '../../optics-deploy/src/core/CoreDeploy';
import * as deploys from '../../optics-deploy/src/core'; import * as deploys from '../../optics-deploy/src/core';

Loading…
Cancel
Save