Commit 5f2fae12 authored by Kelvin Fichter's avatar Kelvin Fichter

feat(sdk): implement sendMessage

parent 22234b1d
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Overrides, Signer, BigNumber } from 'ethers'
import {
TransactionRequest,
TransactionResponse,
} from '@ethersproject/abstract-provider'
import {
CrossChainMessageRequest,
ICrossChainMessenger,
ICrossChainProvider,
L1ToL2Overrides,
MessageLike,
NumberLike,
MessageDirection,
} from './interfaces'
export class CrossChainMessenger implements ICrossChainMessenger {
provider: ICrossChainProvider
l1Signer: Signer
l2Signer: Signer
/**
* Creates a new CrossChainMessenger instance.
*
* @param opts Options for the messenger.
* @param opts.provider CrossChainProvider to use to send messages.
* @param opts.l1Signer Signer to use to send messages on L1.
* @param opts.l2Signer Signer to use to send messages on L2.
*/
constructor(opts: {
provider: ICrossChainProvider
l1Signer: Signer
l2Signer: Signer
}) {
this.provider = opts.provider
this.l1Signer = opts.l1Signer
this.l2Signer = opts.l2Signer
}
public async sendMessage(
message: CrossChainMessageRequest,
overrides?: L1ToL2Overrides
): Promise<TransactionResponse> {
const tx = await this.populateTransaction.sendMessage(message, overrides)
if (message.direction === MessageDirection.L1_TO_L2) {
return this.l1Signer.sendTransaction(tx)
} else {
return this.l2Signer.sendTransaction(tx)
}
}
public async resendMessage(
message: MessageLike,
messageGasLimit: NumberLike,
overrides?: Overrides
): Promise<TransactionResponse> {
throw new Error('Not implemented')
}
public async finalizeMessage(
message: MessageLike,
overrides?: Overrides
): Promise<TransactionResponse> {
throw new Error('Not implemented')
}
public async depositETH(
amount: NumberLike,
overrides?: L1ToL2Overrides
): Promise<TransactionResponse> {
throw new Error('Not implemented')
}
public async withdrawETH(
amount: NumberLike,
overrides?: Overrides
): Promise<TransactionResponse> {
throw new Error('Not implemented')
}
populateTransaction = {
sendMessage: async (
message: CrossChainMessageRequest,
overrides?: L1ToL2Overrides
): Promise<TransactionRequest> => {
if (message.direction === MessageDirection.L1_TO_L2) {
return this.provider.contracts.l1.L1CrossDomainMessenger.connect(
this.l1Signer
).populateTransaction.sendMessage(
message.target,
message.message,
overrides?.l2GasLimit ||
(await this.provider.estimateL2MessageGasLimit(message))
)
} else {
return this.provider.contracts.l2.L2CrossDomainMessenger.connect(
this.l2Signer
).populateTransaction.sendMessage(
message.target,
message.message,
0 // Gas limit goes unused when sending from L2 to L1
)
}
},
resendMessage: async (
message: MessageLike,
messageGasLimit: NumberLike,
overrides?: Overrides
): Promise<TransactionRequest> => {
throw new Error('Not implemented')
},
finalizeMessage: async (
message: MessageLike,
overrides?: Overrides
): Promise<TransactionRequest> => {
throw new Error('Not implemented')
},
depositETH: async (
amount: NumberLike,
overrides?: L1ToL2Overrides
): Promise<TransactionRequest> => {
throw new Error('Not implemented')
},
withdrawETH: async (
amount: NumberLike,
overrides?: Overrides
): Promise<TransactionRequest> => {
throw new Error('Not implemented')
},
}
estimateGas = {
sendMessage: async (
message: CrossChainMessageRequest,
overrides?: L1ToL2Overrides
): Promise<BigNumber> => {
const tx = await this.populateTransaction.sendMessage(message, overrides)
if (message.direction === MessageDirection.L1_TO_L2) {
return this.provider.l1Provider.estimateGas(tx)
} else {
return this.provider.l2Provider.estimateGas(tx)
}
},
resendMessage: async (
message: MessageLike,
messageGasLimit: NumberLike,
overrides?: Overrides
): Promise<BigNumber> => {
throw new Error('Not implemented')
},
finalizeMessage: async (
message: MessageLike,
overrides?: Overrides
): Promise<BigNumber> => {
throw new Error('Not implemented')
},
depositETH: async (
amount: NumberLike,
overrides?: L1ToL2Overrides
): Promise<BigNumber> => {
throw new Error('Not implemented')
},
withdrawETH: async (
amount: NumberLike,
overrides?: Overrides
): Promise<BigNumber> => {
throw new Error('Not implemented')
},
}
}
......@@ -12,11 +12,13 @@ import {
OEContracts,
OEContractsLike,
MessageLike,
MessageRequestLike,
TransactionLike,
AddressLike,
NumberLike,
ProviderLike,
CrossChainMessage,
CrossChainMessageRequest,
MessageDirection,
MessageStatus,
TokenBridgeMessage,
......@@ -464,12 +466,21 @@ export class CrossChainProvider implements ICrossChainProvider {
}
public async estimateL2MessageGasLimit(
message: MessageLike,
message: MessageRequestLike,
opts?: {
bufferPercent?: number
from?: string
}
): Promise<BigNumber> {
const resolved = await this.toCrossChainMessage(message)
let resolved: CrossChainMessage | CrossChainMessageRequest
let from: string
if ((message as CrossChainMessage).messageNonce === undefined) {
resolved = message as CrossChainMessageRequest
from = opts?.from
} else {
resolved = await this.toCrossChainMessage(message as MessageLike)
from = opts?.from || (resolved as CrossChainMessage).sender
}
// L2 message gas estimation is only used for L1 => L2 messages.
if (resolved.direction === MessageDirection.L2_TO_L1) {
......@@ -477,7 +488,7 @@ export class CrossChainProvider implements ICrossChainProvider {
}
const estimate = await this.l2Provider.estimateGas({
from: resolved.sender,
from,
to: resolved.target,
data: resolved.message,
})
......
export * from './interfaces'
export * from './utils'
export * from './cross-chain-provider'
export * from './cross-chain-messenger'
import { Overrides, Signer } from 'ethers'
import { Overrides, Signer, BigNumber } from 'ethers'
import {
TransactionRequest,
TransactionResponse,
......@@ -22,9 +22,14 @@ export interface ICrossChainMessenger {
provider: ICrossChainProvider
/**
* Signer that will carry out L1/L2 transactions.
* Signer that will carry out L1 transactions.
*/
signer: Signer
l1Signer: Signer
/**
* Signer that will carry out L2 transactions.
*/
l2Signer: Signer
/**
* Sends a given cross chain message. Where the message is sent depends on the direction attached
......@@ -107,7 +112,7 @@ export interface ICrossChainMessenger {
sendMessage: (
message: CrossChainMessageRequest,
overrides?: L1ToL2Overrides
) => Promise<TransactionResponse>
) => Promise<TransactionRequest>
/**
* Generates a transaction that resends a given cross chain message. Only applies to L1 to L2
......@@ -178,7 +183,7 @@ export interface ICrossChainMessenger {
sendMessage: (
message: CrossChainMessageRequest,
overrides?: L1ToL2Overrides
) => Promise<TransactionResponse>
) => Promise<BigNumber>
/**
* Estimates gas required to resend a cross chain message. Only applies to L1 to L2 messages.
......@@ -192,7 +197,7 @@ export interface ICrossChainMessenger {
message: MessageLike,
messageGasLimit: NumberLike,
overrides?: Overrides
): Promise<TransactionRequest>
): Promise<BigNumber>
/**
* Estimates gas required to finalize a cross chain message. Only applies to L2 to L1 messages.
......@@ -204,7 +209,7 @@ export interface ICrossChainMessenger {
finalizeMessage(
message: MessageLike,
overrides?: Overrides
): Promise<TransactionRequest>
): Promise<BigNumber>
/**
* Estimates gas required to deposit some ETH into the L2 chain.
......@@ -216,7 +221,7 @@ export interface ICrossChainMessenger {
depositETH(
amount: NumberLike,
overrides?: L1ToL2Overrides
): Promise<TransactionRequest>
): Promise<BigNumber>
/**
* Estimates gas required to withdraw some ETH back to the L1 chain.
......@@ -225,9 +230,6 @@ export interface ICrossChainMessenger {
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to withdraw the tokens.
*/
withdrawETH(
amount: NumberLike,
overrides?: Overrides
): Promise<TransactionRequest>
withdrawETH(amount: NumberLike, overrides?: Overrides): Promise<BigNumber>
}
}
......@@ -3,6 +3,7 @@ import { Provider, BlockTag } from '@ethersproject/abstract-provider'
import {
MessageLike,
MessageRequestLike,
TransactionLike,
AddressLike,
NumberLike,
......@@ -205,12 +206,14 @@ export interface ICrossChainProvider {
* @param message Message get a gas estimate for.
* @param opts Options object.
* @param opts.bufferPercent Percentage of gas to add to the estimate. Defaults to 20.
* @param opts.from Address to use as the sender.
* @returns Estimates L2 gas limit.
*/
estimateL2MessageGasLimit(
message: MessageLike,
message: MessageRequestLike,
opts?: {
bufferPercent?: number
from?: string
}
): Promise<BigNumber>
......
......@@ -143,7 +143,6 @@ export interface CrossChainMessageRequest {
direction: MessageDirection
target: string
message: string
l2GasLimit: NumberLike
}
/**
......@@ -235,7 +234,7 @@ export interface StateRootBatch {
* limit field (gas used depends on the amount of gas provided).
*/
export type L1ToL2Overrides = Overrides & {
l2GasLimit: NumberLike
l2GasLimit?: NumberLike
}
/**
......@@ -244,13 +243,22 @@ export type L1ToL2Overrides = Overrides & {
export type TransactionLike = string | TransactionReceipt | TransactionResponse
/**
* Stuff that can be coerced into a message.
* Stuff that can be coerced into a CrossChainMessage.
*/
export type MessageLike =
| CrossChainMessage
| TransactionLike
| TokenBridgeMessage
/**
* Stuff that can be coerced into a CrossChainMessageRequest.
*/
export type MessageRequestLike =
| CrossChainMessageRequest
| CrossChainMessage
| TransactionLike
| TokenBridgeMessage
/**
* Stuff that can be coerced into a provider.
*/
......
......@@ -7,13 +7,22 @@ contract MockMessenger is ICrossDomainMessenger {
return address(0);
}
uint256 public nonce;
// Empty function to satisfy the interface.
function sendMessage(
address _target,
bytes calldata _message,
uint32 _gasLimit
) public {
return;
emit SentMessage(
_target,
msg.sender,
_message,
nonce,
_gasLimit
);
nonce++;
}
struct SentMessageEventParams {
......
import './setup'
import { Contract } from 'ethers'
import { ethers } from 'hardhat'
import { expect } from './setup'
import {
CrossChainProvider,
CrossChainMessenger,
MessageDirection,
} from '../src'
describe('CrossChainMessenger', () => {
let l1Signer: any
let l2Signer: any
before(async () => {
;[l1Signer, l2Signer] = await ethers.getSigners()
})
describe('sendMessage', () => {
let l1Messenger: Contract
let l2Messenger: Contract
let provider: CrossChainProvider
let messenger: CrossChainMessenger
beforeEach(async () => {
l1Messenger = (await (
await ethers.getContractFactory('MockMessenger')
).deploy()) as any
l2Messenger = (await (
await ethers.getContractFactory('MockMessenger')
).deploy()) as any
provider = new CrossChainProvider({
l1Provider: ethers.provider,
l2Provider: ethers.provider,
l1ChainId: 31337,
contracts: {
l1: {
L1CrossDomainMessenger: l1Messenger.address,
},
l2: {
L2CrossDomainMessenger: l2Messenger.address,
},
},
})
messenger = new CrossChainMessenger({
provider,
l1Signer,
l2Signer,
})
})
describe('when the message is an L1 to L2 message', () => {
describe('when no l2GasLimit is provided', () => {
it('should send a message with an estimated l2GasLimit')
it('should send a message with an estimated l2GasLimit', async () => {
const message = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
message: '0x' + '22'.repeat(32),
}
const estimate = await provider.estimateL2MessageGasLimit(message)
await expect(messenger.sendMessage(message))
.to.emit(l1Messenger, 'SentMessage')
.withArgs(
message.target,
await l1Signer.getAddress(),
message.message,
0,
estimate
)
})
})
describe('when an l2GasLimit is provided', () => {
it('should send a message with the provided l2GasLimit')
it('should send a message with the provided l2GasLimit', async () => {
const message = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
message: '0x' + '22'.repeat(32),
}
await expect(
messenger.sendMessage(message, {
l2GasLimit: 1234,
})
)
.to.emit(l1Messenger, 'SentMessage')
.withArgs(
message.target,
await l1Signer.getAddress(),
message.message,
0,
1234
)
})
})
})
describe('when the message is an L2 to L1 message', () => {
it('should send a message', async () => {
const message = {
direction: MessageDirection.L2_TO_L1,
target: '0x' + '11'.repeat(20),
message: '0x' + '22'.repeat(32),
}
await expect(messenger.sendMessage(message))
.to.emit(l2Messenger, 'SentMessage')
.withArgs(
message.target,
await l2Signer.getAddress(),
message.message,
0,
0
)
})
})
})
......
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