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 importsbuddies-main-deployment
parent
798396fb90
commit
067817f891
@ -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, |
|
||||||
}; |
|
||||||
}); |
}); |
||||||
|
Loading…
Reference in new issue