Commit c2230e99 authored by smartcontracts's avatar smartcontracts Committed by GitHub

Merge pull request #1920 from ethereum-optimism/sc/sdk-contract-utils

feat: add SDK contract utils and corresponding tests
parents 0d5c7693 78bc0e1c
......@@ -33,11 +33,6 @@ export interface ICrossChainProvider {
*/
l1ChainId: number
/**
* Chain ID for the L2 network.
*/
l2ChainId: number
/**
* Contract objects attached to their respective providers and addresses.
*/
......@@ -129,9 +124,9 @@ export interface ICrossChainProvider {
*
* @param message Message to wait for.
* @param opts Options to pass to the waiting function.
* - `confirmations` (number): Number of transaction confirmations to wait for before returning.
* - `pollIntervalMs` (number): Number of milliseconds to wait between polling for the receipt.
* - `loopsBeforeTimeout` (number): Number of times to poll before timing out.
* @param opts.confirmations Number of transaction confirmations to wait for before returning.
* @param opts.pollIntervalMs Number of milliseconds to wait between polling for the receipt.
* @param opts.timeoutMs Milliseconds to wait before timing out.
* @returns CrossChainMessage receipt including receipt of the transaction that relayed the
* given message.
*/
......@@ -140,7 +135,7 @@ export interface ICrossChainProvider {
opts?: {
confirmations?: number
pollIntervalMs?: number
loopsBeforeTimeout?: number
timeoutMs?: number
}
): Promise<MessageReceipt>
......
......@@ -6,37 +6,65 @@ import {
import { Signer } from '@ethersproject/abstract-signer'
import { Contract, BigNumber, Overrides } from 'ethers'
/**
* L1 contract references.
*/
export interface OEL1Contracts {
AddressManager: Contract
L1CrossDomainMessenger: Contract
L1StandardBridge: Contract
StateCommitmentChain: Contract
CanonicalTransactionChain: Contract
BondManager: Contract
}
/**
* L2 contract references.
*/
export interface OEL2Contracts {
L2CrossDomainMessenger: Contract
L2StandardBridge: Contract
OVM_L1BlockNumber: Contract
OVM_L2ToL1MessagePasser: Contract
OVM_DeployerWhitelist: Contract
OVM_ETH: Contract
OVM_GasPriceOracle: Contract
OVM_SequencerFeeVault: Contract
WETH: Contract
}
/**
* Represents Optimistic Ethereum contracts, assumed to be connected to their appropriate
* providers and addresses.
*/
export interface OEContracts {
/**
* L1 contract references.
*/
l1: {
AddressManager: Contract
L1CrossDomainMessenger: Contract
L1StandardBridge: Contract
StateCommitmentChain: Contract
CanonicalTransactionChain: Contract
BondManager: Contract
}
l1: OEL1Contracts
l2: OEL2Contracts
}
/**
* L2 contract references.
*/
l2: {
L2CrossDomainMessenger: Contract
L2StandardBridge: Contract
OVM_L1BlockNumber: Contract
OVM_L2ToL1MessagePasser: Contract
OVM_DeployerWhitelist: Contract
OVM_ETH: Contract
OVM_GasPriceOracle: Contract
OVM_SequencerFeeVault: Contract
WETH: Contract
}
/**
* Convenience type for something that looks like the L1 OE contract interface but could be
* addresses instead of actual contract objects.
*/
export type OEL1ContractsLike = {
[K in keyof OEL1Contracts]: AddressLike
}
/**
* Convenience type for something that looks like the L2 OE contract interface but could be
* addresses instead of actual contract objects.
*/
export type OEL2ContractsLike = {
[K in keyof OEL2Contracts]: AddressLike
}
/**
* Convenience type for something that looks like the OE contract interface but could be
* addresses instead of actual contract objects.
*/
export interface OEContractsLike {
l1: OEL1ContractsLike
l2: OEL2ContractsLike
}
/**
......@@ -164,13 +192,6 @@ export interface StateRootBatch {
stateRoots: string[]
}
/**
* Utility type for deep partials.
*/
export type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>
}
/**
* Extended Ethers overrides object with an l2GasLimit field.
* Only meant to be used for L1 to L2 messages, since L2 to L1 messages don't have a specified gas
......
......@@ -4,46 +4,13 @@ import {
TransactionReceipt,
TransactionResponse,
} from '@ethersproject/abstract-provider'
import { getContractInterface } from '@eth-optimism/contracts'
import { ethers } from 'ethers'
import { ethers, BigNumber } from 'ethers'
import {
ProviderLike,
TransactionLike,
DirectionlessCrossChainMessage,
} from './interfaces'
/**
* Returns the canonical encoding of a cross chain message. This encoding is used in various
* locations within the Optimistic Ethereum smart contracts.
*
* @param message Cross chain message to encode.
* @returns Canonical encoding of the message.
*/
export const encodeCrossChainMessage = (
message: DirectionlessCrossChainMessage
): string => {
return getContractInterface('L2CrossDomainMessenger').encodeFunctionData(
'relayMessage',
[message.target, message.sender, message.message, message.messageNonce]
)
}
/**
* Returns the canonical hash of a cross chain message. This hash is used in various locations
* within the Optimistic Ethereum smart contracts and is the keccak256 hash of the result of
* encodeCrossChainMessage.
*
* @param message Cross chain message to hash.
* @returns Canonical hash of the message.
*/
export const hashCrossChainMessage = (
message: DirectionlessCrossChainMessage
): string => {
return ethers.utils.solidityKeccak256(
['bytes'],
[encodeCrossChainMessage(message)]
)
}
NumberLike,
AddressLike,
} from '../interfaces'
/**
* Converts a ProviderLike into a provider. Assumes that if the ProviderLike is a string then
......@@ -84,3 +51,29 @@ export const toTransactionHash = (transaction: TransactionLike): string => {
throw new Error('Invalid transaction')
}
}
/**
* Converts a number-like into an ethers BigNumber.
*
* @param num Number-like to convert into a BigNumber.
* @returns Number-like as a BigNumber.
*/
export const toBigNumber = (num: NumberLike): BigNumber => {
return ethers.BigNumber.from(num)
}
/**
* Converts an address-like into a 0x-prefixed address string.
*
* @param addr Address-like to convert into an address.
* @returns Address-like as an address.
*/
export const toAddress = (addr: AddressLike): string => {
if (typeof addr === 'string') {
assert(ethers.utils.isAddress(addr), 'Invalid address')
return ethers.utils.getAddress(addr)
} else {
assert(ethers.utils.isAddress(addr.address), 'Invalid address')
return ethers.utils.getAddress(addr.address)
}
}
import { getContractInterface, predeploys } from '@eth-optimism/contracts'
import { ethers, Contract } from 'ethers'
import {
OEContracts,
OEL1Contracts,
OEL2Contracts,
OEContractsLike,
OEL2ContractsLike,
AddressLike,
} from '../interfaces'
import { toAddress } from './coercion'
import { DeepPartial } from './type-utils'
/**
* Full list of default L2 contract addresses.
*/
export const DEFAULT_L2_CONTRACT_ADDRESSES: OEL2ContractsLike = {
L2CrossDomainMessenger: predeploys.L2CrossDomainMessenger,
L2StandardBridge: predeploys.L2StandardBridge,
OVM_L1BlockNumber: predeploys.OVM_L1BlockNumber,
OVM_L2ToL1MessagePasser: predeploys.OVM_L2ToL1MessagePasser,
OVM_DeployerWhitelist: predeploys.OVM_DeployerWhitelist,
OVM_ETH: predeploys.OVM_ETH,
OVM_GasPriceOracle: predeploys.OVM_GasPriceOracle,
OVM_SequencerFeeVault: predeploys.OVM_SequencerFeeVault,
WETH: predeploys.WETH9,
}
/**
* We've changed some contract names in this SDK to be a bit nicer. Here we remap these nicer names
* back to the original contract names so we can look them up.
*/
const NAME_REMAPPING = {
AddressManager: 'Lib_AddressManager',
OVM_L1BlockNumber: 'iOVM_L1BlockNumber',
WETH: 'WETH9',
}
/**
* Mapping of L1 chain IDs to the appropriate contract addresses for the OE deployments to the
* given network. Simplifies the process of getting the correct contract addresses for a given
* contract name.
*/
export const CONTRACT_ADDRESSES: {
[l1ChainId: number]: OEContractsLike
} = {
// Mainnet
1: {
l1: {
AddressManager: '0xdE1FCfB0851916CA5101820A69b13a4E276bd81F',
L1CrossDomainMessenger: '0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1',
L1StandardBridge: '0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1',
StateCommitmentChain: '0xBe5dAb4A2e9cd0F27300dB4aB94BeE3A233AEB19',
CanonicalTransactionChain: '0x5E4e65926BA27467555EB562121fac00D24E9dD2',
BondManager: '0xcd626E1328b41fCF24737F137BcD4CE0c32bc8d1',
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
},
// Kovan
42: {
l1: {
AddressManager: '0x100Dd3b414Df5BbA2B542864fF94aF8024aFdf3a',
L1CrossDomainMessenger: '0x4361d0F75A0186C05f971c566dC6bEa5957483fD',
L1StandardBridge: '0x22F24361D548e5FaAfb36d1437839f080363982B',
StateCommitmentChain: '0xD7754711773489F31A0602635f3F167826ce53C5',
CanonicalTransactionChain: '0xf7B88A133202d41Fe5E2Ab22e6309a1A4D50AF74',
BondManager: '0xc5a603d273E28185c18Ba4d26A0024B2d2F42740',
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
},
// Goerli
5: {
l1: {
AddressManager: '0x2F7E3cAC91b5148d336BbffB224B4dC79F09f01D',
L1CrossDomainMessenger: '0xEcC89b9EDD804850C4F343A278Be902be11AaF42',
L1StandardBridge: '0x73298186A143a54c20ae98EEE5a025bD5979De02',
StateCommitmentChain: '0x1afcA918eff169eE20fF8AB6Be75f3E872eE1C1A',
CanonicalTransactionChain: '0x2ebA8c4EfDB39A8Cd8f9eD65c50ec079f7CEBD81',
BondManager: '0xE5AE60bD6F8DEe4D0c2BC9268e23B92F1cacC58F',
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
},
}
/**
* Returns an ethers.Contract object for the given name, connected to the appropriate address for
* the given L1 chain ID. Users can also provide a custom address to connect the contract to
* instead. If the chain ID is not known then the user MUST provide a custom address or this
* function will throw an error.
*
* @param contractName Name of the contract to connect to.
* @param l1ChainId Chain ID for the L1 network where the OE contracts are deployed.
* @param opts Additional options for connecting to the contract.
* @param opts.address Custom address to connect to the contract.
* @param opts.signerOrProvider Signer or provider to connect to the contract.
* @returns An ethers.Contract object connected to the appropriate address and interface.
*/
export const getOEContract = (
contractName: keyof OEL1Contracts | keyof OEL2Contracts,
l1ChainId: number,
opts: {
address?: AddressLike
signerOrProvider?: ethers.Signer | ethers.providers.Provider
} = {}
): Contract => {
const addresses = CONTRACT_ADDRESSES[l1ChainId]
if (addresses === undefined && opts.address === undefined) {
throw new Error(
`cannot get contract ${contractName} for unknown L1 chain ID ${l1ChainId}, you must provide an address`
)
}
return new Contract(
toAddress(
opts.address || addresses.l1[contractName] || addresses.l2[contractName]
),
getContractInterface(NAME_REMAPPING[contractName] || contractName),
opts.signerOrProvider
)
}
/**
* Automatically connects to all contract addresses, both L1 and L2, for the given L1 chain ID. The
* user can provide custom contract address overrides for L1 or L2 contracts. If the given chain ID
* is not known then the user MUST provide custom contract addresses for ALL L1 contracts or this
* function will throw an error.
*
* @param l1ChainId Chain ID for the L1 network where the OE contracts are deployed.
* @param opts Additional options for connecting to the contracts.
* @param opts.l1SignerOrProvider: Signer or provider to connect to the L1 contracts.
* @param opts.l2SignerOrProvider: Signer or provider to connect to the L2 contracts.
* @param opts.overrides Custom contract address overrides for L1 or L2 contracts.
* @returns An object containing ethers.Contract objects connected to the appropriate addresses on
* both L1 and L2.
*/
export const getAllOEContracts = (
l1ChainId: number,
opts: {
l1SignerOrProvider?: ethers.Signer | ethers.providers.Provider
l2SignerOrProvider?: ethers.Signer | ethers.providers.Provider
overrides?: DeepPartial<OEContractsLike>
} = {}
): OEContracts => {
const addresses = CONTRACT_ADDRESSES[l1ChainId] || {
l1: {
AddressManager: undefined,
L1CrossDomainMessenger: undefined,
L1StandardBridge: undefined,
StateCommitmentChain: undefined,
CanonicalTransactionChain: undefined,
BondManager: undefined,
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}
// Attach all L1 contracts.
const l1Contracts: OEL1Contracts = {} as any
for (const [contractName, contractAddress] of Object.entries(addresses.l1)) {
l1Contracts[contractName] = getOEContract(contractName as any, l1ChainId, {
address: opts.overrides?.l1?.[contractName] || contractAddress,
signerOrProvider: opts.l1SignerOrProvider,
})
}
// Attach all L2 contracts.
const l2Contracts: OEL2Contracts = {} as any
for (const [contractName, contractAddress] of Object.entries(addresses.l2)) {
l2Contracts[contractName] = getOEContract(contractName as any, l1ChainId, {
address: opts.overrides?.l2?.[contractName] || contractAddress,
signerOrProvider: opts.l2SignerOrProvider,
})
}
return {
l1: l1Contracts,
l2: l2Contracts,
}
}
export * from './coercion'
export * from './contracts'
export * from './message-encoding'
export * from './type-utils'
import { getContractInterface } from '@eth-optimism/contracts'
import { ethers } from 'ethers'
import { DirectionlessCrossChainMessage } from '../interfaces'
/**
* Returns the canonical encoding of a cross chain message. This encoding is used in various
* locations within the Optimistic Ethereum smart contracts.
*
* @param message Cross chain message to encode.
* @returns Canonical encoding of the message.
*/
export const encodeCrossChainMessage = (
message: DirectionlessCrossChainMessage
): string => {
return getContractInterface('L2CrossDomainMessenger').encodeFunctionData(
'relayMessage',
[message.target, message.sender, message.message, message.messageNonce]
)
}
/**
* Returns the canonical hash of a cross chain message. This hash is used in various locations
* within the Optimistic Ethereum smart contracts and is the keccak256 hash of the result of
* encodeCrossChainMessage.
*
* @param message Cross chain message to hash.
* @returns Canonical hash of the message.
*/
export const hashCrossChainMessage = (
message: DirectionlessCrossChainMessage
): string => {
return ethers.utils.solidityKeccak256(
['bytes'],
[encodeCrossChainMessage(message)]
)
}
/**
* Utility type for deep partials.
*/
export type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>
}
......@@ -3,16 +3,52 @@ import './setup'
describe('CrossChainProvider', () => {
describe('construction', () => {
describe('basic construction (given L1 and L2 providers)', () => {
it('should have an l1Provider', () => {})
describe('when given an ethers provider for the L1 provider', () => {
it('should use the provider as the L1 provider', () => {})
})
describe('when given an ethers provider for the L2 provider', () => {
it('should use the provider as the L2 provider', () => {})
})
describe('when given a string as the L1 provider', () => {
it('should create a JSON-RPC provider for the L1 provider', () => {})
})
describe('when given a string as the L2 provider', () => {
it('should create a JSON-RPC provider for the L2 provider', () => {})
})
describe('when no custom contract addresses are provided', () => {
describe('when given a known chain ID', () => {
it('should use the contract addresses for the known chain ID', () => {})
})
it('should have an l2Provider', () => {})
describe('when given an unknown chain ID', () => {
it('should throw an error', () => {})
})
})
it('should have an l1ChainId', () => {})
describe('when custom contract addresses are provided', () => {
describe('when given a known chain ID', () => {
it('should use known addresses except where custom addresses are given', () => {})
})
it('should have an l2ChainId', () => {})
describe('when given an unknown chain ID', () => {
describe('when all L1 addresses are provided', () => {
describe('when not all L2 addresses are provided', () => {
it('should use the custom L1 addresses and the default L2 addresses', () => {})
})
it('should have all contract connections', () => {})
describe('when all L2 addresses are provided', () => {
it('should use the custom addresses everywhere', () => {})
})
})
describe('when not all L1 addresses are provided', () => {
it('should throw an error', () => {})
})
})
})
})
......@@ -152,7 +188,7 @@ describe('CrossChainProvider', () => {
})
describe('when the message has not been relayed', () => {
it('should return a status of READY_FOR_RELAY')
it('should return a status of READY_FOR_RELAY', () => {})
})
})
})
......
import { expect } from './setup'
import { expect } from '../setup'
import { Provider } from '@ethersproject/abstract-provider'
import { Contract, Signer } from 'ethers'
import { Contract } from 'ethers'
import { ethers } from 'hardhat'
import { getContractFactory } from '@eth-optimism/contracts'
import {
toProvider,
toTransactionHash,
CrossChainMessage,
MessageDirection,
encodeCrossChainMessage,
hashCrossChainMessage,
} from '../src'
describe('utils', () => {
let signers: Signer[]
before(async () => {
signers = (await ethers.getSigners()) as any
})
describe('encodeCrossChainMessage', () => {
let Lib_CrossDomainUtils: Contract
before(async () => {
Lib_CrossDomainUtils = (await getContractFactory(
'TestLib_CrossDomainUtils',
signers[0]
).deploy()) as any
})
it('should properly encode a message', async () => {
const message: CrossChainMessage = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '1234'.repeat(32),
messageNonce: 1234,
}
const actual = encodeCrossChainMessage(message)
const expected = await Lib_CrossDomainUtils.encodeXDomainCalldata(
message.target,
message.sender,
message.message,
message.messageNonce
)
expect(actual).to.equal(expected)
})
})
describe('hashCrossChainMessage', () => {
let MessageEncodingHelper: Contract
before(async () => {
MessageEncodingHelper = (await (
await ethers.getContractFactory('MessageEncodingHelper')
).deploy()) as any
})
it('should properly hash a message', async () => {
const message: CrossChainMessage = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '1234'.repeat(32),
messageNonce: 1234,
}
const actual = hashCrossChainMessage(message)
const expected = await MessageEncodingHelper.hashXDomainCalldata(
message.target,
message.sender,
message.message,
message.messageNonce
)
expect(actual).to.equal(expected)
})
})
import { toProvider, toTransactionHash } from '../../src'
describe('type coercion utils', () => {
describe('toProvider', () => {
it('should convert a string to a JsonRpcProvider', () => {
const provider = toProvider('http://localhost:8545')
......
/* eslint-disable @typescript-eslint/no-empty-function */
import { expect } from '../setup'
import {
getOEContract,
getAllOEContracts,
CONTRACT_ADDRESSES,
DEFAULT_L2_CONTRACT_ADDRESSES,
} from '../../src'
import { Signer } from 'ethers'
import { ethers } from 'hardhat'
describe('contract connection utils', () => {
let signers: Signer[]
before(async () => {
signers = (await ethers.getSigners()) as any
})
describe('getOEContract', () => {
describe('when given a known chain ID', () => {
describe('when not given an address override', () => {
it('should use the address for the given contract name and chain ID', () => {
const addresses = CONTRACT_ADDRESSES[1]
for (const [contractName, contractAddress] of [
...Object.entries(addresses.l1),
...Object.entries(addresses.l2),
]) {
const contract = getOEContract(contractName as any, 1)
expect(contract.address).to.equal(contractAddress)
}
})
})
describe('when given an address override', () => {
it('should use the custom address', () => {
const addresses = CONTRACT_ADDRESSES[1]
for (const contractName of [
...Object.keys(addresses.l1),
...Object.keys(addresses.l2),
]) {
const address = '0x' + '11'.repeat(20)
const contract = getOEContract(contractName as any, 1, {
address,
})
expect(contract.address).to.equal(address)
}
})
})
})
describe('when given an unknown chain ID', () => {
describe('when not given an address override', () => {
it('should throw an error', () => {
expect(() => getOEContract('L1CrossDomainMessenger', 3)).to.throw()
})
})
describe('when given an address override', () => {
it('should use the custom address', () => {
const address = '0x' + '11'.repeat(20)
const contract = getOEContract('L1CrossDomainMessenger', 3, {
address,
})
expect(contract.address).to.equal(address)
})
})
})
describe('when connected to a valid address', () => {
it('should have the correct interface for the contract name', () => {
const contract = getOEContract('L1CrossDomainMessenger', 1)
expect(contract.sendMessage).to.not.be.undefined
})
describe('when not given a signer or provider', () => {
it('should not have a signer or provider', () => {
const contract = getOEContract('L1CrossDomainMessenger', 1)
expect(contract.signer).to.be.null
expect(contract.provider).to.be.null
})
})
describe('when given a signer', () => {
it('should attach the given signer', () => {
const contract = getOEContract('L1CrossDomainMessenger', 1, {
signerOrProvider: signers[0],
})
expect(contract.signer).to.deep.equal(signers[0])
})
})
describe('when given a provider', () => {
it('should attach the given provider', () => {
const contract = getOEContract('L1CrossDomainMessenger', 1, {
signerOrProvider: ethers.provider as any,
})
expect(contract.signer).to.be.null
expect(contract.provider).to.deep.equal(ethers.provider)
})
})
})
})
describe('getAllOEContracts', () => {
describe('when given a known chain ID', () => {
describe('when not given any address overrides', () => {
it('should return all contracts connected to the default addresses', () => {
const contracts = getAllOEContracts(1)
const addresses = CONTRACT_ADDRESSES[1]
for (const [contractName, contractAddress] of Object.entries(
addresses.l1
)) {
const contract = contracts.l1[contractName]
expect(contract.address).to.equal(contractAddress)
}
for (const [contractName, contractAddress] of Object.entries(
addresses.l2
)) {
const contract = contracts.l2[contractName]
expect(contract.address).to.equal(contractAddress)
}
})
})
describe('when given address overrides', () => {
it('should return contracts connected to the overridden addresses where given', () => {
const overrides = {
l1: {
L1CrossDomainMessenger: '0x' + '11'.repeat(20),
},
l2: {
L2CrossDomainMessenger: '0x' + '22'.repeat(20),
},
}
const contracts = getAllOEContracts(1, { overrides })
const addresses = CONTRACT_ADDRESSES[1]
for (const [contractName, contractAddress] of Object.entries(
addresses.l1
)) {
const contract = contracts.l1[contractName]
if (overrides.l1[contractName]) {
expect(contract.address).to.equal(overrides.l1[contractName])
} else {
expect(contract.address).to.equal(contractAddress)
}
}
for (const [contractName, contractAddress] of Object.entries(
addresses.l2
)) {
const contract = contracts.l2[contractName]
if (overrides.l2[contractName]) {
expect(contract.address).to.equal(overrides.l2[contractName])
} else {
expect(contract.address).to.equal(contractAddress)
}
}
})
})
})
describe('when given an unknown chain ID', () => {
describe('when given address overrides for all L1 contracts', () => {
describe('when given address overrides for L2 contracts', () => {
it('should return contracts connected to the overridden addresses where given', () => {
const l1Overrides = {}
for (const contractName of Object.keys(CONTRACT_ADDRESSES[1].l1)) {
l1Overrides[contractName] = '0x' + '11'.repeat(20)
}
const contracts = getAllOEContracts(3, {
overrides: {
l1: l1Overrides as any,
l2: {
L2CrossDomainMessenger: '0x' + '22'.repeat(20),
},
},
})
for (const [contractName, contract] of Object.entries(
contracts.l1
)) {
expect(contract.address).to.equal(l1Overrides[contractName])
}
expect(contracts.l2.L2CrossDomainMessenger.address).to.equal(
'0x' + '22'.repeat(20)
)
})
})
describe('when not given address overrides for L2 contracts', () => {
it('should return contracts connected to the default L2 addresses and custom L1 addresses', () => {
const l1Overrides = {}
for (const contractName of Object.keys(CONTRACT_ADDRESSES[1].l1)) {
l1Overrides[contractName] = '0x' + '11'.repeat(20)
}
const contracts = getAllOEContracts(3, {
overrides: {
l1: l1Overrides as any,
},
})
for (const [contractName, contract] of Object.entries(
contracts.l1
)) {
expect(contract.address).to.equal(l1Overrides[contractName])
}
for (const [contractName, contract] of Object.entries(
contracts.l2
)) {
expect(contract.address).to.equal(
DEFAULT_L2_CONTRACT_ADDRESSES[contractName]
)
}
})
})
})
describe('when given address overrides for some L1 contracts', () => {
it('should throw an error', () => {
expect(() =>
getAllOEContracts(3, {
overrides: {
l1: {
L1CrossDomainMessenger: '0x' + '11'.repeat(20),
},
},
})
).to.throw()
})
})
describe('when given address overrides for no L1 contracts', () => {
it('should throw an error', () => {
expect(() => getAllOEContracts(3)).to.throw()
})
})
})
describe('when not given a signer or provider', () => {
it('should not attach a signer or provider to any contracts', () => {
const contracts = getAllOEContracts(1)
for (const contract of Object.values(contracts.l1)) {
expect(contract.signer).to.be.null
expect(contract.provider).to.be.null
}
for (const contract of Object.values(contracts.l2)) {
expect(contract.signer).to.be.null
expect(contract.provider).to.be.null
}
})
})
describe('when given an L1 signer', () => {
it('should attach the signer to the L1 contracts only', () => {
const contracts = getAllOEContracts(1, {
l1SignerOrProvider: signers[0],
})
for (const contract of Object.values(contracts.l1)) {
expect(contract.signer).to.deep.equal(signers[0])
}
for (const contract of Object.values(contracts.l2)) {
expect(contract.signer).to.be.null
expect(contract.provider).to.be.null
}
})
})
describe('when given an L2 signer', () => {
it('should attach the signer to the L2 contracts only', () => {
const contracts = getAllOEContracts(1, {
l2SignerOrProvider: signers[0],
})
for (const contract of Object.values(contracts.l1)) {
expect(contract.signer).to.be.null
expect(contract.provider).to.be.null
}
for (const contract of Object.values(contracts.l2)) {
expect(contract.signer).to.deep.equal(signers[0])
}
})
})
describe('when given an L1 signer and an L2 signer', () => {
it('should attach the signer to both sets of contracts', () => {
const contracts = getAllOEContracts(1, {
l1SignerOrProvider: signers[0],
l2SignerOrProvider: signers[1],
})
for (const contract of Object.values(contracts.l1)) {
expect(contract.signer).to.deep.equal(signers[0])
}
for (const contract of Object.values(contracts.l2)) {
expect(contract.signer).to.deep.equal(signers[1])
}
})
})
describe('when given an L1 provider', () => {
it('should attach the provider to the L1 contracts only', () => {
const contracts = getAllOEContracts(1, {
l1SignerOrProvider: ethers.provider as any,
})
for (const contract of Object.values(contracts.l1)) {
expect(contract.signer).to.be.null
expect(contract.provider).to.deep.equal(ethers.provider)
}
for (const contract of Object.values(contracts.l2)) {
expect(contract.signer).to.be.null
expect(contract.provider).to.be.null
}
})
})
describe('when given an L2 provider', () => {
it('should attach the provider to the L2 contracts only', () => {
const contracts = getAllOEContracts(1, {
l2SignerOrProvider: ethers.provider as any,
})
for (const contract of Object.values(contracts.l1)) {
expect(contract.signer).to.be.null
expect(contract.provider).to.be.null
}
for (const contract of Object.values(contracts.l2)) {
expect(contract.signer).to.be.null
expect(contract.provider).to.deep.equal(ethers.provider)
}
})
})
describe('when given an L1 provider and an L2 provider', () => {
it('should attach the provider to both sets of contracts', () => {
const contracts = getAllOEContracts(1, {
l1SignerOrProvider: ethers.provider as any,
l2SignerOrProvider: ethers.provider as any,
})
for (const contract of Object.values(contracts.l1)) {
expect(contract.signer).to.be.null
expect(contract.provider).to.deep.equal(ethers.provider)
}
for (const contract of Object.values(contracts.l2)) {
expect(contract.signer).to.be.null
expect(contract.provider).to.deep.equal(ethers.provider)
}
})
})
})
})
import { expect } from '../setup'
import { Contract, Signer } from 'ethers'
import { ethers } from 'hardhat'
import { getContractFactory } from '@eth-optimism/contracts'
import {
CrossChainMessage,
MessageDirection,
encodeCrossChainMessage,
hashCrossChainMessage,
} from '../../src'
describe('message encoding utils', () => {
let signers: Signer[]
before(async () => {
signers = (await ethers.getSigners()) as any
})
describe('encodeCrossChainMessage', () => {
let Lib_CrossDomainUtils: Contract
before(async () => {
Lib_CrossDomainUtils = (await getContractFactory(
'TestLib_CrossDomainUtils',
signers[0]
).deploy()) as any
})
it('should properly encode a message', async () => {
const message: CrossChainMessage = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '1234'.repeat(32),
messageNonce: 1234,
}
const actual = encodeCrossChainMessage(message)
const expected = await Lib_CrossDomainUtils.encodeXDomainCalldata(
message.target,
message.sender,
message.message,
message.messageNonce
)
expect(actual).to.equal(expected)
})
})
describe('hashCrossChainMessage', () => {
let MessageEncodingHelper: Contract
before(async () => {
MessageEncodingHelper = (await (
await ethers.getContractFactory('MessageEncodingHelper')
).deploy()) as any
})
it('should properly hash a message', async () => {
const message: CrossChainMessage = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '1234'.repeat(32),
messageNonce: 1234,
}
const actual = hashCrossChainMessage(message)
const expected = await MessageEncodingHelper.hashXDomainCalldata(
message.target,
message.sender,
message.message,
message.messageNonce
)
expect(actual).to.equal(expected)
})
})
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment