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 { 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<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], |
||||
); |
||||
} |
||||
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; |
||||
}); |
||||
|
Loading…
Reference in new issue