@hyperlane-xyz/core
Version:
Core solidity contracts for Hyperlane
352 lines (316 loc) • 9.27 kB
text/typescript
import { BigNumber, BigNumberish, Contract, ContractTransaction } from 'ethers'
import { providers } from 'ethers'
import { assert, expect } from 'chai'
import hre, { ethers, network } from 'hardhat'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import cbor from 'cbor'
import { LinkToken } from '../../typechain'
/**
* Convert string to hex bytes
* @param data string to convert to hex bytes
*/
export function stringToBytes(data: string): string {
return ethers.utils.hexlify(ethers.utils.toUtf8Bytes(data))
}
/**
* Add a hex prefix to a hex string
*
* @param hex The hex string to prepend the hex prefix to
*/
export function addHexPrefix(hex: string): string {
return hex.startsWith('0x') ? hex : `0x${hex}`
}
/**
* Convert a number value to bytes32 format
*
* @param num The number value to convert to bytes32 format
*/
export function numToBytes32(
num: Parameters<typeof ethers.utils.hexlify>[0],
): string {
const hexNum = ethers.utils.hexlify(num)
const strippedNum = stripHexPrefix(hexNum)
if (strippedNum.length > 32 * 2) {
throw Error(
'Cannot convert number to bytes32 format, value is greater than maximum bytes32 value',
)
}
return addHexPrefix(strippedNum.padStart(32 * 2, '0'))
}
/**
* Retrieve single log from transaction
*
* @param tx The transaction to wait for, then extract logs from
* @param index The index of the log to retrieve
*/
export async function getLog(
tx: ContractTransaction,
index: number,
): Promise<providers.Log> {
const logs = await getLogs(tx)
if (!logs[index]) {
throw Error('unable to extract log from transaction receipt')
}
return logs[index]
}
/**
* Extract array of logs from a transaction
*
* @param tx The transaction to wait for, then extract logs from
*/
export async function getLogs(
tx: ContractTransaction,
): Promise<providers.Log[]> {
const receipt = await tx.wait()
if (!receipt.logs) {
throw Error('unable to extract logs from transaction receipt')
}
return receipt.logs
}
/**
* Convert a UTF-8 string into a bytes32 hex string representation
*
* The inverse function of [[parseBytes32String]]
*
* @param args The UTF-8 string representation to convert to a bytes32 hex string representation
*/
export function toBytes32String(
...args: Parameters<typeof ethers.utils.formatBytes32String>
): ReturnType<typeof ethers.utils.formatBytes32String> {
return ethers.utils.formatBytes32String(...args)
}
/**
* Strip the leading 0x hex prefix from a hex string
*
* @param hex The hex string to strip the leading hex prefix out of
*/
export function stripHexPrefix(hex: string): string {
if (!ethers.utils.isHexString(hex)) {
throw Error(`Expected valid hex string, got: "${hex}"`)
}
return hex.replace('0x', '')
}
/**
* Create a buffer from a hex string
*
* @param hexstr The hex string to convert to a buffer
*/
export function hexToBuf(hexstr: string): Buffer {
return Buffer.from(stripHexPrefix(hexstr), 'hex')
}
/**
* Decodes a CBOR hex string, and adds opening and closing brackets to the CBOR if they are not present.
*
* @param hexstr The hex string to decode
*/
export function decodeDietCBOR(hexstr: string) {
const buf = hexToBuf(hexstr)
return cbor.decodeFirstSync(addCBORMapDelimiters(buf))
}
/**
* Add a starting and closing map characters to a CBOR encoding if they are not already present.
*/
export function addCBORMapDelimiters(buffer: Buffer): Buffer {
if (buffer[0] >> 5 === 5) {
return buffer
}
/**
* This is the opening character of a CBOR map.
* @see https://en.wikipedia.org/wiki/CBOR#CBOR_data_item_header
*/
const startIndefiniteLengthMap = Buffer.from([0xbf])
/**
* This is the closing character in a CBOR map.
* @see https://en.wikipedia.org/wiki/CBOR#CBOR_data_item_header
*/
const endIndefiniteLengthMap = Buffer.from([0xff])
return Buffer.concat(
[startIndefiniteLengthMap, buffer, endIndefiniteLengthMap],
buffer.length + 2,
)
}
/**
* Convert an Ether value to a wei amount
*
* @param args Ether value to convert to an Ether amount
*/
export function toWei(
...args: Parameters<typeof ethers.utils.parseEther>
): ReturnType<typeof ethers.utils.parseEther> {
return ethers.utils.parseEther(...args)
}
/**
* Converts any number, BigNumber, hex string or Arrayish to a hex string.
*
* @param args Value to convert to a hex string
*/
export function toHex(
...args: Parameters<typeof ethers.utils.hexlify>
): ReturnType<typeof ethers.utils.hexlify> {
return ethers.utils.hexlify(...args)
}
/**
* Increase the current time within the evm to 5 minutes past the current time
*
* @param provider The ethers provider to send the time increase request to
*/
export async function increaseTime5Minutes(
provider: providers.JsonRpcProvider,
): Promise<void> {
await increaseTimeBy(5 * 60, provider)
}
/**
* Increase the current time within the evm to "n" seconds past the current time
*
* @param seconds The number of seconds to increase to the current time by
* @param provider The ethers provider to send the time increase request to
*/
export async function increaseTimeBy(
seconds: number,
provider: providers.JsonRpcProvider,
) {
await provider.send('evm_increaseTime', [seconds])
}
/**
* Instruct the provider to mine an additional block
*
* @param provider The ethers provider to instruct to mine an additional block
*/
export async function mineBlock(provider: providers.JsonRpcProvider) {
await provider.send('evm_mine', [])
}
/**
* Parse out an evm word (32 bytes) into an address (20 bytes) representation
*
* @param hex The evm word in hex string format to parse the address
* out of.
*/
export function evmWordToAddress(hex?: string): string {
if (!hex) {
throw Error('Input not defined')
}
assert.equal(hex.slice(0, 26), '0x000000000000000000000000')
return ethers.utils.getAddress(hex.slice(26))
}
/**
* Check that a contract's abi exposes the expected interface.
*
* @param contract The contract with the actual abi to check the expected exposed methods and getters against.
* @param expectedPublic The expected public exposed methods and getters to match against the actual abi.
*/
export function publicAbi(
contract: Contract,
expectedPublic: string[],
): boolean {
const actualPublic = []
for (const m in contract.functions) {
if (!m.includes('(')) {
actualPublic.push(m)
}
}
for (const method of actualPublic) {
const index = expectedPublic.indexOf(method)
assert.isAtLeast(index, 0, `#${method} is NOT expected to be public`)
}
for (const method of expectedPublic) {
const index = actualPublic.indexOf(method)
assert.isAtLeast(index, 0, `#${method} is expected to be public`)
}
return true
}
/**
* Converts an L1 address to an Arbitrum L2 address
*
* @param l1Address Address on L1
*/
export function toArbitrumL2AliasAddress(l1Address: string): string {
return ethers.utils.getAddress(
BigNumber.from(l1Address)
.add('0x1111000000000000000000000000000000001111')
.toHexString()
.replace('0x01', '0x'),
)
}
/**
* Lets you impersonate and sign transactions from any account.
*
* @param address Address to impersonate
*/
export async function impersonateAs(
address: string,
): Promise<SignerWithAddress> {
await hre.network.provider.request({
method: 'hardhat_impersonateAccount',
params: [address],
})
return await ethers.getSigner(address)
}
export async function stopImpersonateAs(address: string): Promise<void> {
await hre.network.provider.request({
method: 'hardhat_stopImpersonatingAccount',
params: [address],
})
}
export async function assertBalance(
address: string,
balance: BigNumberish,
msg?: string,
) {
expect(await ethers.provider.getBalance(address)).equal(balance, msg)
}
export async function assertLinkTokenBalance(
lt: LinkToken,
address: string,
balance: BigNumberish,
msg?: string,
) {
expect(await lt.balanceOf(address)).equal(balance, msg)
}
export async function assertSubscriptionBalance(
coordinator: Contract,
subID: BigNumberish,
balance: BigNumberish,
msg?: string,
) {
expect((await coordinator.getSubscription(subID)).balance).deep.equal(
balance,
msg,
)
}
export async function setTimestamp(timestamp: number) {
await network.provider.request({
method: 'evm_setNextBlockTimestamp',
params: [timestamp],
})
await network.provider.request({
method: 'evm_mine',
params: [],
})
}
export async function fastForward(duration: number) {
await network.provider.request({
method: 'evm_increaseTime',
params: [duration],
})
await network.provider.request({
method: 'evm_mine',
params: [],
})
}
export async function reset() {
await network.provider.request({
method: 'hardhat_reset',
params: [],
})
}
export function expectGasWithinDeviation(
gasUsed: BigNumber,
expectedGas: BigNumberish,
deviation: BigNumberish = 100,
) {
const expected: BigNumber = BigNumber.from(expectedGas)
expect(gasUsed).is.gt(expected.sub(deviation)).and.lt(expected.add(deviation))
}
export function randomAddress() {
return ethers.Wallet.createRandom().address
}