Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
77bd8dfc
Unverified
Commit
77bd8dfc
authored
Feb 01, 2022
by
Kelvin Fichter
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(sdk): refactor into single messenger class
parent
ceea12f3
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
2389 additions
and
2416 deletions
+2389
-2416
dai-bridge.ts
packages/sdk/src/adapters/dai-bridge.ts
+1
-1
standard-bridge.ts
packages/sdk/src/adapters/standard-bridge.ts
+10
-10
cross-chain-messenger.ts
packages/sdk/src/cross-chain-messenger.ts
+704
-48
cross-chain-provider.ts
packages/sdk/src/cross-chain-provider.ts
+0
-654
index.ts
packages/sdk/src/index.ts
+0
-1
bridge-adapter.ts
packages/sdk/src/interfaces/bridge-adapter.ts
+2
-2
cross-chain-messenger.ts
packages/sdk/src/interfaces/cross-chain-messenger.ts
+314
-8
cross-chain-provider.ts
packages/sdk/src/interfaces/cross-chain-provider.ts
+0
-299
index.ts
packages/sdk/src/interfaces/index.ts
+0
-1
types.ts
packages/sdk/src/interfaces/types.ts
+2
-2
coercion.ts
packages/sdk/src/utils/coercion.ts
+15
-10
contracts.ts
packages/sdk/src/utils/contracts.ts
+4
-4
cross-chain-messenger.spec.ts
packages/sdk/test/cross-chain-messenger.spec.ts
+1333
-53
cross-chain-provider.spec.ts
packages/sdk/test/cross-chain-provider.spec.ts
+0
-1319
coercion.spec.ts
packages/sdk/test/utils/coercion.spec.ts
+4
-4
No files found.
packages/sdk/src/adapters/dai-bridge.ts
View file @
77bd8dfc
...
@@ -45,7 +45,7 @@ export class DAIBridgeAdapter extends StandardBridgeAdapter {
...
@@ -45,7 +45,7 @@ export class DAIBridgeAdapter extends StandardBridgeAdapter {
type
:
'
function
'
,
type
:
'
function
'
,
},
},
],
],
this
.
provid
er
.
l1Provider
this
.
messeng
er
.
l1Provider
)
)
const
allowedL1Token
=
await
l1Bridge
.
l1Token
()
const
allowedL1Token
=
await
l1Bridge
.
l1Token
()
...
...
packages/sdk/src/adapters/standard-bridge.ts
View file @
77bd8dfc
...
@@ -10,7 +10,7 @@ import { hexStringEquals } from '@eth-optimism/core-utils'
...
@@ -10,7 +10,7 @@ import { hexStringEquals } from '@eth-optimism/core-utils'
import
{
import
{
IBridgeAdapter
,
IBridgeAdapter
,
ICrossChain
Provid
er
,
ICrossChain
Messeng
er
,
NumberLike
,
NumberLike
,
AddressLike
,
AddressLike
,
TokenBridgeMessage
,
TokenBridgeMessage
,
...
@@ -22,7 +22,7 @@ import { toAddress } from '../utils'
...
@@ -22,7 +22,7 @@ import { toAddress } from '../utils'
* Bridge adapter for any token bridge that uses the standard token bridge interface.
* Bridge adapter for any token bridge that uses the standard token bridge interface.
*/
*/
export
class
StandardBridgeAdapter
implements
IBridgeAdapter
{
export
class
StandardBridgeAdapter
implements
IBridgeAdapter
{
public
provider
:
ICrossChainProvid
er
public
messenger
:
ICrossChainMesseng
er
public
l1Bridge
:
Contract
public
l1Bridge
:
Contract
public
l2Bridge
:
Contract
public
l2Bridge
:
Contract
...
@@ -30,25 +30,25 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
...
@@ -30,25 +30,25 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
* Creates a StandardBridgeAdapter instance.
* Creates a StandardBridgeAdapter instance.
*
*
* @param opts Options for the adapter.
* @param opts Options for the adapter.
* @param opts.
provid
er Provider used to make queries related to cross-chain interactions.
* @param opts.
messeng
er Provider used to make queries related to cross-chain interactions.
* @param opts.l1Bridge L1 bridge contract.
* @param opts.l1Bridge L1 bridge contract.
* @param opts.l2Bridge L2 bridge contract.
* @param opts.l2Bridge L2 bridge contract.
*/
*/
constructor
(
opts
:
{
constructor
(
opts
:
{
provider
:
ICrossChainProvid
er
messenger
:
ICrossChainMesseng
er
l1Bridge
:
AddressLike
l1Bridge
:
AddressLike
l2Bridge
:
AddressLike
l2Bridge
:
AddressLike
})
{
})
{
this
.
provider
=
opts
.
provid
er
this
.
messenger
=
opts
.
messeng
er
this
.
l1Bridge
=
new
Contract
(
this
.
l1Bridge
=
new
Contract
(
toAddress
(
opts
.
l1Bridge
),
toAddress
(
opts
.
l1Bridge
),
getContractInterface
(
'
L1StandardBridge
'
),
getContractInterface
(
'
L1StandardBridge
'
),
this
.
provid
er
.
l1Provider
this
.
messeng
er
.
l1Provider
)
)
this
.
l2Bridge
=
new
Contract
(
this
.
l2Bridge
=
new
Contract
(
toAddress
(
opts
.
l2Bridge
),
toAddress
(
opts
.
l2Bridge
),
getContractInterface
(
'
IL2ERC20Bridge
'
),
getContractInterface
(
'
IL2ERC20Bridge
'
),
this
.
provid
er
.
l2Provider
this
.
messeng
er
.
l2Provider
)
)
}
}
...
@@ -167,7 +167,7 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
...
@@ -167,7 +167,7 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
const
contract
=
new
Contract
(
const
contract
=
new
Contract
(
toAddress
(
l2Token
),
toAddress
(
l2Token
),
getContractInterface
(
'
L2StandardERC20
'
),
getContractInterface
(
'
L2StandardERC20
'
),
this
.
provid
er
.
l2Provider
this
.
messeng
er
.
l2Provider
)
)
// Don't support ETH deposits or withdrawals via this bridge.
// Don't support ETH deposits or withdrawals via this bridge.
...
@@ -287,7 +287,7 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
...
@@ -287,7 +287,7 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
BigNumber
>
=>
{
):
Promise
<
BigNumber
>
=>
{
return
this
.
provid
er
.
l1Provider
.
estimateGas
(
return
this
.
messeng
er
.
l1Provider
.
estimateGas
(
await
this
.
populateTransaction
.
deposit
(
l1Token
,
l2Token
,
amount
,
opts
)
await
this
.
populateTransaction
.
deposit
(
l1Token
,
l2Token
,
amount
,
opts
)
)
)
},
},
...
@@ -300,7 +300,7 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
...
@@ -300,7 +300,7 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
BigNumber
>
=>
{
):
Promise
<
BigNumber
>
=>
{
return
this
.
provid
er
.
l2Provider
.
estimateGas
(
return
this
.
messeng
er
.
l2Provider
.
estimateGas
(
await
this
.
populateTransaction
.
withdraw
(
l1Token
,
l2Token
,
amount
,
opts
)
await
this
.
populateTransaction
.
withdraw
(
l1Token
,
l2Token
,
amount
,
opts
)
)
)
},
},
...
...
packages/sdk/src/cross-chain-messenger.ts
View file @
77bd8dfc
import
{
ethers
,
Overrides
,
Signer
,
BigNumber
}
from
'
ethers
'
/* eslint-disable @typescript-eslint/no-unused-vars */
import
{
import
{
TransactionRequest
,
Provider
,
BlockTag
,
TransactionReceipt
,
TransactionResponse
,
TransactionResponse
,
TransactionRequest
,
}
from
'
@ethersproject/abstract-provider
'
}
from
'
@ethersproject/abstract-provider
'
import
{
Signer
}
from
'
@ethersproject/abstract-signer
'
import
{
ethers
,
BigNumber
,
Overrides
}
from
'
ethers
'
import
{
sleep
,
remove0x
}
from
'
@eth-optimism/core-utils
'
import
{
predeploys
}
from
'
@eth-optimism/contracts
'
import
{
predeploys
}
from
'
@eth-optimism/contracts
'
import
{
import
{
CrossChainMessageRequest
,
ICrossChainMessenger
,
ICrossChainMessenger
,
ICrossChainProvider
,
OEContracts
,
OEContractsLike
,
MessageLike
,
MessageLike
,
NumberLike
,
MessageRequestLike
,
TransactionLike
,
AddressLike
,
AddressLike
,
NumberLike
,
SignerOrProviderLike
,
CrossChainMessage
,
CrossChainMessageRequest
,
CrossChainMessageProof
,
MessageDirection
,
MessageDirection
,
MessageStatus
,
TokenBridgeMessage
,
MessageReceipt
,
MessageReceiptStatus
,
BridgeAdapterData
,
BridgeAdapters
,
StateRoot
,
StateRootBatch
,
IBridgeAdapter
,
}
from
'
./interfaces
'
}
from
'
./interfaces
'
import
{
toSignerOrProvider
,
toBigNumber
,
toTransactionHash
,
DeepPartial
,
getAllOEContracts
,
getBridgeAdapters
,
hashCrossChainMessage
,
makeMerkleTreeProof
,
makeStateTrieProof
,
encodeCrossChainMessage
,
}
from
'
./utils
'
export
class
CrossChainMessenger
implements
ICrossChainMessenger
{
export
class
CrossChainMessenger
implements
ICrossChainMessenger
{
provider
:
ICrossChainProvider
public
l1SignerOrProvider
:
Signer
|
Provider
l1Signer
:
Signer
public
l2SignerOrProvider
:
Signer
|
Provider
l2Signer
:
Signer
public
l1ChainId
:
number
public
contracts
:
OEContracts
public
bridges
:
BridgeAdapters
/**
/**
* Creates a new CrossChain
Messeng
er instance.
* Creates a new CrossChain
Provid
er instance.
*
*
* @param opts Options for the messenger.
* @param opts Options for the provider.
* @param opts.provider CrossChainProvider to use to send messages.
* @param opts.l1SignerOrProvider Signer or Provider for the L1 chain, or a JSON-RPC url.
* @param opts.l1Signer Signer to use to send messages on L1.
* @param opts.l1SignerOrProvider Signer or Provider for the L2 chain, or a JSON-RPC url.
* @param opts.l2Signer Signer to use to send messages on L2.
* @param opts.l1ChainId Chain ID for the L1 chain.
* @param opts.contracts Optional contract address overrides.
* @param opts.bridges Optional bridge address list.
*/
*/
constructor
(
opts
:
{
constructor
(
opts
:
{
provider
:
ICrossChainProvider
l1SignerOrProvider
:
SignerOrProviderLike
l1Signer
:
Signer
l2SignerOrProvider
:
SignerOrProviderLike
l2Signer
:
Signer
l1ChainId
:
NumberLike
contracts
?:
DeepPartial
<
OEContractsLike
>
bridges
?:
BridgeAdapterData
})
{
})
{
this
.
provider
=
opts
.
provider
this
.
l1SignerOrProvider
=
toSignerOrProvider
(
opts
.
l1SignerOrProvider
)
this
.
l1Signer
=
opts
.
l1Signer
this
.
l2SignerOrProvider
=
toSignerOrProvider
(
opts
.
l2SignerOrProvider
)
this
.
l2Signer
=
opts
.
l2Signer
this
.
l1ChainId
=
toBigNumber
(
opts
.
l1ChainId
).
toNumber
()
this
.
contracts
=
getAllOEContracts
(
this
.
l1ChainId
,
{
l1SignerOrProvider
:
this
.
l1SignerOrProvider
,
l2SignerOrProvider
:
this
.
l2SignerOrProvider
,
overrides
:
opts
.
contracts
,
})
this
.
bridges
=
getBridgeAdapters
(
this
.
l1ChainId
,
this
,
{
overrides
:
opts
.
bridges
,
})
}
get
l1Provider
():
Provider
{
if
(
Provider
.
isProvider
(
this
.
l1SignerOrProvider
))
{
return
this
.
l1SignerOrProvider
}
else
{
return
this
.
l1SignerOrProvider
.
provider
}
}
get
l2Provider
():
Provider
{
if
(
Provider
.
isProvider
(
this
.
l2SignerOrProvider
))
{
return
this
.
l2SignerOrProvider
}
else
{
return
this
.
l2SignerOrProvider
.
provider
}
}
get
l1Signer
():
Signer
{
if
(
Provider
.
isProvider
(
this
.
l1SignerOrProvider
))
{
throw
new
Error
(
`messenger has no L1 signer`
)
}
else
{
return
this
.
l1SignerOrProvider
}
}
get
l2Signer
():
Signer
{
if
(
Provider
.
isProvider
(
this
.
l2SignerOrProvider
))
{
throw
new
Error
(
`messenger has no L2 signer`
)
}
else
{
return
this
.
l2SignerOrProvider
}
}
public
async
getMessagesByTransaction
(
transaction
:
TransactionLike
,
opts
:
{
direction
?:
MessageDirection
}
=
{}
):
Promise
<
CrossChainMessage
[]
>
{
const
txHash
=
toTransactionHash
(
transaction
)
let
receipt
:
TransactionReceipt
if
(
opts
.
direction
!==
undefined
)
{
// Get the receipt for the requested direction.
if
(
opts
.
direction
===
MessageDirection
.
L1_TO_L2
)
{
receipt
=
await
this
.
l1Provider
.
getTransactionReceipt
(
txHash
)
}
else
{
receipt
=
await
this
.
l2Provider
.
getTransactionReceipt
(
txHash
)
}
}
else
{
// Try both directions, starting with L1 => L2.
receipt
=
await
this
.
l1Provider
.
getTransactionReceipt
(
txHash
)
if
(
receipt
)
{
opts
.
direction
=
MessageDirection
.
L1_TO_L2
}
else
{
receipt
=
await
this
.
l2Provider
.
getTransactionReceipt
(
txHash
)
opts
.
direction
=
MessageDirection
.
L2_TO_L1
}
}
if
(
!
receipt
)
{
throw
new
Error
(
`unable to find transaction receipt for
${
txHash
}
`
)
}
// By this point opts.direction will always be defined.
const
messenger
=
opts
.
direction
===
MessageDirection
.
L1_TO_L2
?
this
.
contracts
.
l1
.
L1CrossDomainMessenger
:
this
.
contracts
.
l2
.
L2CrossDomainMessenger
return
receipt
.
logs
.
filter
((
log
)
=>
{
// Only look at logs emitted by the messenger address
return
log
.
address
===
messenger
.
address
})
.
filter
((
log
)
=>
{
// Only look at SentMessage logs specifically
const
parsed
=
messenger
.
interface
.
parseLog
(
log
)
return
parsed
.
name
===
'
SentMessage
'
})
.
map
((
log
)
=>
{
// Convert each SentMessage log into a message object
const
parsed
=
messenger
.
interface
.
parseLog
(
log
)
return
{
direction
:
opts
.
direction
,
target
:
parsed
.
args
.
target
,
sender
:
parsed
.
args
.
sender
,
message
:
parsed
.
args
.
message
,
messageNonce
:
parsed
.
args
.
messageNonce
,
gasLimit
:
parsed
.
args
.
gasLimit
,
logIndex
:
log
.
logIndex
,
blockNumber
:
log
.
blockNumber
,
transactionHash
:
log
.
transactionHash
,
}
})
}
public
async
getMessagesByAddress
(
address
:
AddressLike
,
opts
?:
{
direction
?:
MessageDirection
fromBlock
?:
NumberLike
toBlock
?:
NumberLike
}
):
Promise
<
CrossChainMessage
[]
>
{
throw
new
Error
(
'
Not implemented
'
)
}
public
async
getBridgeForTokenPair
(
l1Token
:
AddressLike
,
l2Token
:
AddressLike
):
Promise
<
IBridgeAdapter
>
{
const
bridges
:
IBridgeAdapter
[]
=
[]
for
(
const
bridge
of
Object
.
values
(
this
.
bridges
))
{
if
(
await
bridge
.
supportsTokenPair
(
l1Token
,
l2Token
))
{
bridges
.
push
(
bridge
)
}
}
if
(
bridges
.
length
===
0
)
{
throw
new
Error
(
`no supported bridge for token pair`
)
}
if
(
bridges
.
length
>
1
)
{
throw
new
Error
(
`found more than one bridge for token pair`
)
}
return
bridges
[
0
]
}
public
async
getTokenBridgeMessagesByAddress
(
address
:
AddressLike
,
opts
:
{
direction
?:
MessageDirection
}
=
{}
):
Promise
<
TokenBridgeMessage
[]
>
{
return
(
await
Promise
.
all
(
Object
.
values
(
this
.
bridges
).
map
(
async
(
bridge
)
=>
{
return
bridge
.
getTokenBridgeMessagesByAddress
(
address
,
opts
)
})
)
).
reduce
((
acc
,
val
)
=>
{
return
acc
.
concat
(
val
)
},
[])
}
public
async
getDepositsByAddress
(
address
:
AddressLike
,
opts
:
{
fromBlock
?:
BlockTag
toBlock
?:
BlockTag
}
=
{}
):
Promise
<
TokenBridgeMessage
[]
>
{
return
(
await
Promise
.
all
(
Object
.
values
(
this
.
bridges
).
map
(
async
(
bridge
)
=>
{
return
bridge
.
getDepositsByAddress
(
address
,
opts
)
})
)
).
reduce
((
acc
,
val
)
=>
{
return
acc
.
concat
(
val
)
},
[])
}
public
async
getWithdrawalsByAddress
(
address
:
AddressLike
,
opts
:
{
fromBlock
?:
BlockTag
toBlock
?:
BlockTag
}
=
{}
):
Promise
<
TokenBridgeMessage
[]
>
{
return
(
await
Promise
.
all
(
Object
.
values
(
this
.
bridges
).
map
(
async
(
bridge
)
=>
{
return
bridge
.
getWithdrawalsByAddress
(
address
,
opts
)
})
)
).
reduce
((
acc
,
val
)
=>
{
return
acc
.
concat
(
val
)
},
[])
}
public
async
toCrossChainMessage
(
message
:
MessageLike
):
Promise
<
CrossChainMessage
>
{
// TODO: Convert these checks into proper type checks.
if
((
message
as
CrossChainMessage
).
message
)
{
return
message
as
CrossChainMessage
}
else
if
(
(
message
as
TokenBridgeMessage
).
l1Token
&&
(
message
as
TokenBridgeMessage
).
l2Token
&&
(
message
as
TokenBridgeMessage
).
transactionHash
)
{
const
messages
=
await
this
.
getMessagesByTransaction
(
(
message
as
TokenBridgeMessage
).
transactionHash
)
// The `messages` object corresponds to a list of SentMessage events that were triggered by
// the same transaction. We want to find the specific SentMessage event that corresponds to
// the TokenBridgeMessage (either a ETHDepositInitiated, ERC20DepositInitiated, or
// WithdrawalInitiated event). We expect the behavior of bridge contracts to be that these
// TokenBridgeMessage events are triggered and then a SentMessage event is triggered. Our
// goal here is therefore to find the first SentMessage event that comes after the input
// event.
const
found
=
messages
.
sort
((
a
,
b
)
=>
{
// Sort all messages in ascending order by log index.
return
a
.
logIndex
-
b
.
logIndex
})
.
find
((
m
)
=>
{
return
m
.
logIndex
>
(
message
as
TokenBridgeMessage
).
logIndex
})
if
(
!
found
)
{
throw
new
Error
(
`could not find SentMessage event for message`
)
}
return
found
}
else
{
// TODO: Explicit TransactionLike check and throw if not TransactionLike
const
messages
=
await
this
.
getMessagesByTransaction
(
message
as
TransactionLike
)
// We only want to treat TransactionLike objects as MessageLike if they only emit a single
// message (very common). It's unintuitive to treat a TransactionLike as a MessageLike if
// they emit more than one message (which message do you pick?), so we throw an error.
if
(
messages
.
length
!==
1
)
{
throw
new
Error
(
`expected 1 message, got
${
messages
.
length
}
`
)
}
return
messages
[
0
]
}
}
public
async
getMessageStatus
(
message
:
MessageLike
):
Promise
<
MessageStatus
>
{
const
resolved
=
await
this
.
toCrossChainMessage
(
message
)
const
receipt
=
await
this
.
getMessageReceipt
(
resolved
)
if
(
resolved
.
direction
===
MessageDirection
.
L1_TO_L2
)
{
if
(
receipt
===
null
)
{
return
MessageStatus
.
UNCONFIRMED_L1_TO_L2_MESSAGE
}
else
{
if
(
receipt
.
receiptStatus
===
MessageReceiptStatus
.
RELAYED_SUCCEEDED
)
{
return
MessageStatus
.
RELAYED
}
else
{
return
MessageStatus
.
FAILED_L1_TO_L2_MESSAGE
}
}
}
else
{
if
(
receipt
===
null
)
{
const
stateRoot
=
await
this
.
getMessageStateRoot
(
resolved
)
if
(
stateRoot
===
null
)
{
return
MessageStatus
.
STATE_ROOT_NOT_PUBLISHED
}
else
{
const
challengePeriod
=
await
this
.
getChallengePeriodSeconds
()
const
targetBlock
=
await
this
.
l1Provider
.
getBlock
(
stateRoot
.
batch
.
blockNumber
)
const
latestBlock
=
await
this
.
l1Provider
.
getBlock
(
'
latest
'
)
if
(
targetBlock
.
timestamp
+
challengePeriod
>
latestBlock
.
timestamp
)
{
return
MessageStatus
.
IN_CHALLENGE_PERIOD
}
else
{
return
MessageStatus
.
READY_FOR_RELAY
}
}
}
else
{
if
(
receipt
.
receiptStatus
===
MessageReceiptStatus
.
RELAYED_SUCCEEDED
)
{
return
MessageStatus
.
RELAYED
}
else
{
return
MessageStatus
.
READY_FOR_RELAY
}
}
}
}
public
async
getMessageReceipt
(
message
:
MessageLike
):
Promise
<
MessageReceipt
>
{
const
resolved
=
await
this
.
toCrossChainMessage
(
message
)
const
messageHash
=
hashCrossChainMessage
(
resolved
)
// Here we want the messenger that will receive the message, not the one that sent it.
const
messenger
=
resolved
.
direction
===
MessageDirection
.
L1_TO_L2
?
this
.
contracts
.
l2
.
L2CrossDomainMessenger
:
this
.
contracts
.
l1
.
L1CrossDomainMessenger
const
relayedMessageEvents
=
await
messenger
.
queryFilter
(
messenger
.
filters
.
RelayedMessage
(
messageHash
)
)
// Great, we found the message. Convert it into a transaction receipt.
if
(
relayedMessageEvents
.
length
===
1
)
{
return
{
receiptStatus
:
MessageReceiptStatus
.
RELAYED_SUCCEEDED
,
transactionReceipt
:
await
relayedMessageEvents
[
0
].
getTransactionReceipt
(),
}
}
else
if
(
relayedMessageEvents
.
length
>
1
)
{
// Should never happen!
throw
new
Error
(
`multiple successful relays for message`
)
}
// We didn't find a transaction that relayed the message. We now attempt to find
// FailedRelayedMessage events instead.
const
failedRelayedMessageEvents
=
await
messenger
.
queryFilter
(
messenger
.
filters
.
FailedRelayedMessage
(
messageHash
)
)
// A transaction can fail to be relayed multiple times. We'll always return the last
// transaction that attempted to relay the message.
// TODO: Is this the best way to handle this?
if
(
failedRelayedMessageEvents
.
length
>
0
)
{
return
{
receiptStatus
:
MessageReceiptStatus
.
RELAYED_FAILED
,
transactionReceipt
:
await
failedRelayedMessageEvents
[
failedRelayedMessageEvents
.
length
-
1
].
getTransactionReceipt
(),
}
}
// TODO: If the user doesn't provide enough gas then there's a chance that FailedRelayedMessage
// will never be triggered. We should probably fix this at the contract level by requiring a
// minimum amount of input gas and designing the contracts such that the gas will always be
// enough to trigger the event. However, for now we need a temporary way to find L1 => L2
// transactions that fail but don't alert us because they didn't provide enough gas.
// TODO: Talk with the systems and protocol team about coordinating a hard fork that fixes this
// on both L1 and L2.
// Just return null if we didn't find a receipt. Slightly nicer than throwing an error.
return
null
}
public
async
waitForMessageReceipt
(
message
:
MessageLike
,
opts
:
{
confirmations
?:
number
pollIntervalMs
?:
number
timeoutMs
?:
number
}
=
{}
):
Promise
<
MessageReceipt
>
{
let
totalTimeMs
=
0
while
(
totalTimeMs
<
(
opts
.
timeoutMs
||
Infinity
))
{
const
tick
=
Date
.
now
()
const
receipt
=
await
this
.
getMessageReceipt
(
message
)
if
(
receipt
!==
null
)
{
return
receipt
}
else
{
await
sleep
(
opts
.
pollIntervalMs
||
4000
)
totalTimeMs
+=
Date
.
now
()
-
tick
}
}
throw
new
Error
(
`timed out waiting for message receipt`
)
}
public
async
estimateL2MessageGasLimit
(
message
:
MessageRequestLike
,
opts
?:
{
bufferPercent
?:
number
from
?:
string
}
):
Promise
<
BigNumber
>
{
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
)
{
throw
new
Error
(
`cannot estimate gas limit for L2 => L1 message`
)
}
const
estimate
=
await
this
.
l2Provider
.
estimateGas
({
from
,
to
:
resolved
.
target
,
data
:
resolved
.
message
,
})
// Return the estimate plus a buffer of 20% just in case.
const
bufferPercent
=
opts
?.
bufferPercent
||
20
return
estimate
.
mul
(
100
+
bufferPercent
).
div
(
100
)
}
public
async
estimateMessageWaitTimeSeconds
(
message
:
MessageLike
):
Promise
<
number
>
{
throw
new
Error
(
'
Not implemented
'
)
}
public
async
getChallengePeriodSeconds
():
Promise
<
number
>
{
const
challengePeriod
=
await
this
.
contracts
.
l1
.
StateCommitmentChain
.
FRAUD_PROOF_WINDOW
()
return
challengePeriod
.
toNumber
()
}
public
async
getMessageStateRoot
(
message
:
MessageLike
):
Promise
<
StateRoot
|
null
>
{
const
resolved
=
await
this
.
toCrossChainMessage
(
message
)
// State roots are only a thing for L2 to L1 messages.
if
(
resolved
.
direction
===
MessageDirection
.
L1_TO_L2
)
{
throw
new
Error
(
`cannot get a state root for an L1 to L2 message`
)
}
// We need the block number of the transaction that triggered the message so we can look up the
// state root batch that corresponds to that block number.
const
messageTxReceipt
=
await
this
.
l2Provider
.
getTransactionReceipt
(
resolved
.
transactionHash
)
// Every block has exactly one transaction in it. Since there's a genesis block, the
// transaction index will always be one less than the block number.
const
messageTxIndex
=
messageTxReceipt
.
blockNumber
-
1
// Pull down the state root batch, we'll try to pick out the specific state root that
// corresponds to our message.
const
stateRootBatch
=
await
this
.
getStateRootBatchByTransactionIndex
(
messageTxIndex
)
// No state root batch, no state root.
if
(
stateRootBatch
===
null
)
{
return
null
}
// We have a state root batch, now we need to find the specific state root for our transaction.
// First we need to figure out the index of the state root within the batch we found. This is
// going to be the original transaction index offset by the total number of previous state
// roots.
const
indexInBatch
=
messageTxIndex
-
stateRootBatch
.
header
.
prevTotalElements
.
toNumber
()
// Just a sanity check.
if
(
stateRootBatch
.
stateRoots
.
length
<=
indexInBatch
)
{
// Should never happen!
throw
new
Error
(
`state root does not exist in batch`
)
}
return
{
stateRoot
:
stateRootBatch
.
stateRoots
[
indexInBatch
],
stateRootIndexInBatch
:
indexInBatch
,
batch
:
stateRootBatch
,
}
}
public
async
getStateBatchAppendedEventByBatchIndex
(
batchIndex
:
number
):
Promise
<
ethers
.
Event
|
null
>
{
const
events
=
await
this
.
contracts
.
l1
.
StateCommitmentChain
.
queryFilter
(
this
.
contracts
.
l1
.
StateCommitmentChain
.
filters
.
StateBatchAppended
(
batchIndex
)
)
if
(
events
.
length
===
0
)
{
return
null
}
else
if
(
events
.
length
>
1
)
{
// Should never happen!
throw
new
Error
(
`found more than one StateBatchAppended event`
)
}
else
{
return
events
[
0
]
}
}
public
async
getStateBatchAppendedEventByTransactionIndex
(
transactionIndex
:
number
):
Promise
<
ethers
.
Event
|
null
>
{
const
isEventHi
=
(
event
:
ethers
.
Event
,
index
:
number
)
=>
{
const
prevTotalElements
=
event
.
args
.
_prevTotalElements
.
toNumber
()
return
index
<
prevTotalElements
}
const
isEventLo
=
(
event
:
ethers
.
Event
,
index
:
number
)
=>
{
const
prevTotalElements
=
event
.
args
.
_prevTotalElements
.
toNumber
()
const
batchSize
=
event
.
args
.
_batchSize
.
toNumber
()
return
index
>=
prevTotalElements
+
batchSize
}
const
totalBatches
:
ethers
.
BigNumber
=
await
this
.
contracts
.
l1
.
StateCommitmentChain
.
getTotalBatches
()
if
(
totalBatches
.
eq
(
0
))
{
return
null
}
let
lowerBound
=
0
let
upperBound
=
totalBatches
.
toNumber
()
-
1
let
batchEvent
:
ethers
.
Event
|
null
=
await
this
.
getStateBatchAppendedEventByBatchIndex
(
upperBound
)
if
(
isEventLo
(
batchEvent
,
transactionIndex
))
{
// Upper bound is too low, means this transaction doesn't have a corresponding state batch yet.
return
null
}
else
if
(
!
isEventHi
(
batchEvent
,
transactionIndex
))
{
// Upper bound is not too low and also not too high. This means the upper bound event is the
// one we're looking for! Return it.
return
batchEvent
}
// Binary search to find the right event. The above checks will guarantee that the event does
// exist and that we'll find it during this search.
while
(
lowerBound
<
upperBound
)
{
const
middleOfBounds
=
Math
.
floor
((
lowerBound
+
upperBound
)
/
2
)
batchEvent
=
await
this
.
getStateBatchAppendedEventByBatchIndex
(
middleOfBounds
)
if
(
isEventHi
(
batchEvent
,
transactionIndex
))
{
upperBound
=
middleOfBounds
}
else
if
(
isEventLo
(
batchEvent
,
transactionIndex
))
{
lowerBound
=
middleOfBounds
}
else
{
break
}
}
return
batchEvent
}
public
async
getStateRootBatchByTransactionIndex
(
transactionIndex
:
number
):
Promise
<
StateRootBatch
|
null
>
{
const
stateBatchAppendedEvent
=
await
this
.
getStateBatchAppendedEventByTransactionIndex
(
transactionIndex
)
if
(
stateBatchAppendedEvent
===
null
)
{
return
null
}
const
stateBatchTransaction
=
await
stateBatchAppendedEvent
.
getTransaction
()
const
[
stateRoots
]
=
this
.
contracts
.
l1
.
StateCommitmentChain
.
interface
.
decodeFunctionData
(
'
appendStateBatch
'
,
stateBatchTransaction
.
data
)
return
{
blockNumber
:
stateBatchAppendedEvent
.
blockNumber
,
stateRoots
,
header
:
{
batchIndex
:
stateBatchAppendedEvent
.
args
.
_batchIndex
,
batchRoot
:
stateBatchAppendedEvent
.
args
.
_batchRoot
,
batchSize
:
stateBatchAppendedEvent
.
args
.
_batchSize
,
prevTotalElements
:
stateBatchAppendedEvent
.
args
.
_prevTotalElements
,
extraData
:
stateBatchAppendedEvent
.
args
.
_extraData
,
},
}
}
public
async
getMessageProof
(
message
:
MessageLike
):
Promise
<
CrossChainMessageProof
>
{
const
resolved
=
await
this
.
toCrossChainMessage
(
message
)
if
(
resolved
.
direction
===
MessageDirection
.
L1_TO_L2
)
{
throw
new
Error
(
`can only generate proofs for L2 to L1 messages`
)
}
const
stateRoot
=
await
this
.
getMessageStateRoot
(
resolved
)
if
(
stateRoot
===
null
)
{
throw
new
Error
(
`state root for message not yet published`
)
}
// We need to calculate the specific storage slot that demonstrates that this message was
// actually included in the L2 chain. The following calculation is based on the fact that
// messages are stored in the following mapping on L2:
// https://github.com/ethereum-optimism/optimism/blob/c84d3450225306abbb39b4e7d6d82424341df2be/packages/contracts/contracts/L2/predeploys/OVM_L2ToL1MessagePasser.sol#L23
// You can read more about how Solidity storage slots are computed for mappings here:
// https://docs.soliditylang.org/en/v0.8.4/internals/layout_in_storage.html#mappings-and-dynamic-arrays
const
messageSlot
=
ethers
.
utils
.
keccak256
(
ethers
.
utils
.
keccak256
(
encodeCrossChainMessage
(
resolved
)
+
remove0x
(
this
.
contracts
.
l2
.
L2CrossDomainMessenger
.
address
)
)
+
'
00
'
.
repeat
(
32
)
)
const
stateTrieProof
=
await
makeStateTrieProof
(
this
.
l2Provider
as
any
,
resolved
.
blockNumber
,
this
.
contracts
.
l2
.
OVM_L2ToL1MessagePasser
.
address
,
messageSlot
)
return
{
stateRoot
:
stateRoot
.
stateRoot
,
stateRootBatchHeader
:
stateRoot
.
batch
.
header
,
stateRootProof
:
{
index
:
stateRoot
.
stateRootIndexInBatch
,
siblings
:
makeMerkleTreeProof
(
stateRoot
.
batch
.
stateRoots
,
stateRoot
.
stateRootIndexInBatch
),
},
stateTrieWitness
:
stateTrieProof
.
accountProof
,
storageTrieWitness
:
stateTrieProof
.
storageProof
,
}
}
}
public
async
sendMessage
(
public
async
sendMessage
(
message
:
CrossChainMessageRequest
,
message
:
CrossChainMessageRequest
,
opts
?:
{
opts
?:
{
signer
?:
Signer
l2GasLimit
?:
NumberLike
l2GasLimit
?:
NumberLike
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
TransactionResponse
>
{
):
Promise
<
TransactionResponse
>
{
const
tx
=
await
this
.
populateTransaction
.
sendMessage
(
message
,
opts
)
const
tx
=
await
this
.
populateTransaction
.
sendMessage
(
message
,
opts
)
if
(
message
.
direction
===
MessageDirection
.
L1_TO_L2
)
{
if
(
message
.
direction
===
MessageDirection
.
L1_TO_L2
)
{
return
this
.
l1Signer
.
sendTransaction
(
tx
)
return
(
opts
?.
signer
||
this
.
l1Signer
)
.
sendTransaction
(
tx
)
}
else
{
}
else
{
return
this
.
l2Signer
.
sendTransaction
(
tx
)
return
(
opts
?.
signer
||
this
.
l2Signer
)
.
sendTransaction
(
tx
)
}
}
}
}
...
@@ -57,10 +708,11 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -57,10 +708,11 @@ export class CrossChainMessenger implements ICrossChainMessenger {
message
:
MessageLike
,
message
:
MessageLike
,
messageGasLimit
:
NumberLike
,
messageGasLimit
:
NumberLike
,
opts
?:
{
opts
?:
{
signer
?:
Signer
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
TransactionResponse
>
{
):
Promise
<
TransactionResponse
>
{
return
this
.
l1Signer
.
sendTransaction
(
return
(
opts
?.
signer
||
this
.
l1Signer
)
.
sendTransaction
(
await
this
.
populateTransaction
.
resendMessage
(
await
this
.
populateTransaction
.
resendMessage
(
message
,
message
,
messageGasLimit
,
messageGasLimit
,
...
@@ -72,10 +724,11 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -72,10 +724,11 @@ export class CrossChainMessenger implements ICrossChainMessenger {
public
async
finalizeMessage
(
public
async
finalizeMessage
(
message
:
MessageLike
,
message
:
MessageLike
,
opts
?:
{
opts
?:
{
signer
?:
Signer
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
TransactionResponse
>
{
):
Promise
<
TransactionResponse
>
{
return
this
.
l1Signer
.
sendTransaction
(
return
(
opts
?.
signer
||
this
.
l1Signer
)
.
sendTransaction
(
await
this
.
populateTransaction
.
finalizeMessage
(
message
,
opts
)
await
this
.
populateTransaction
.
finalizeMessage
(
message
,
opts
)
)
)
}
}
...
@@ -83,11 +736,12 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -83,11 +736,12 @@ export class CrossChainMessenger implements ICrossChainMessenger {
public
async
depositETH
(
public
async
depositETH
(
amount
:
NumberLike
,
amount
:
NumberLike
,
opts
?:
{
opts
?:
{
signer
?:
Signer
l2GasLimit
?:
NumberLike
l2GasLimit
?:
NumberLike
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
TransactionResponse
>
{
):
Promise
<
TransactionResponse
>
{
return
this
.
l1Signer
.
sendTransaction
(
return
(
opts
?.
signer
||
this
.
l1Signer
)
.
sendTransaction
(
await
this
.
populateTransaction
.
depositETH
(
amount
,
opts
)
await
this
.
populateTransaction
.
depositETH
(
amount
,
opts
)
)
)
}
}
...
@@ -95,10 +749,11 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -95,10 +749,11 @@ export class CrossChainMessenger implements ICrossChainMessenger {
public
async
withdrawETH
(
public
async
withdrawETH
(
amount
:
NumberLike
,
amount
:
NumberLike
,
opts
?:
{
opts
?:
{
signer
?:
Signer
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
TransactionResponse
>
{
):
Promise
<
TransactionResponse
>
{
return
this
.
l2Signer
.
sendTransaction
(
return
(
opts
?.
signer
||
this
.
l2Signer
)
.
sendTransaction
(
await
this
.
populateTransaction
.
withdrawETH
(
amount
,
opts
)
await
this
.
populateTransaction
.
withdrawETH
(
amount
,
opts
)
)
)
}
}
...
@@ -108,11 +763,12 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -108,11 +763,12 @@ export class CrossChainMessenger implements ICrossChainMessenger {
l2Token
:
AddressLike
,
l2Token
:
AddressLike
,
amount
:
NumberLike
,
amount
:
NumberLike
,
opts
?:
{
opts
?:
{
signer
?:
Signer
l2GasLimit
?:
NumberLike
l2GasLimit
?:
NumberLike
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
TransactionResponse
>
{
):
Promise
<
TransactionResponse
>
{
return
this
.
l1Signer
.
sendTransaction
(
return
(
opts
?.
signer
||
this
.
l1Signer
)
.
sendTransaction
(
await
this
.
populateTransaction
.
depositERC20
(
await
this
.
populateTransaction
.
depositERC20
(
l1Token
,
l1Token
,
l2Token
,
l2Token
,
...
@@ -127,10 +783,11 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -127,10 +783,11 @@ export class CrossChainMessenger implements ICrossChainMessenger {
l2Token
:
AddressLike
,
l2Token
:
AddressLike
,
amount
:
NumberLike
,
amount
:
NumberLike
,
opts
?:
{
opts
?:
{
signer
?:
Signer
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
TransactionResponse
>
{
):
Promise
<
TransactionResponse
>
{
return
this
.
l2Signer
.
sendTransaction
(
return
(
opts
?.
signer
||
this
.
l2Signer
)
.
sendTransaction
(
await
this
.
populateTransaction
.
withdrawERC20
(
await
this
.
populateTransaction
.
withdrawERC20
(
l1Token
,
l1Token
,
l2Token
,
l2Token
,
...
@@ -149,15 +806,14 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -149,15 +806,14 @@ export class CrossChainMessenger implements ICrossChainMessenger {
}
}
):
Promise
<
TransactionRequest
>
=>
{
):
Promise
<
TransactionRequest
>
=>
{
if
(
message
.
direction
===
MessageDirection
.
L1_TO_L2
)
{
if
(
message
.
direction
===
MessageDirection
.
L1_TO_L2
)
{
return
this
.
provider
.
contracts
.
l1
.
L1CrossDomainMessenger
.
populateTransaction
.
sendMessage
(
return
this
.
contracts
.
l1
.
L1CrossDomainMessenger
.
populateTransaction
.
sendMessage
(
message
.
target
,
message
.
target
,
message
.
message
,
message
.
message
,
opts
?.
l2GasLimit
||
opts
?.
l2GasLimit
||
(
await
this
.
estimateL2MessageGasLimit
(
message
)),
(
await
this
.
provider
.
estimateL2MessageGasLimit
(
message
)),
opts
?.
overrides
||
{}
opts
?.
overrides
||
{}
)
)
}
else
{
}
else
{
return
this
.
provider
.
contracts
.
l2
.
L2CrossDomainMessenger
.
populateTransaction
.
sendMessage
(
return
this
.
contracts
.
l2
.
L2CrossDomainMessenger
.
populateTransaction
.
sendMessage
(
message
.
target
,
message
.
target
,
message
.
message
,
message
.
message
,
0
,
// Gas limit goes unused when sending from L2 to L1
0
,
// Gas limit goes unused when sending from L2 to L1
...
@@ -173,12 +829,12 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -173,12 +829,12 @@ export class CrossChainMessenger implements ICrossChainMessenger {
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
TransactionRequest
>
=>
{
):
Promise
<
TransactionRequest
>
=>
{
const
resolved
=
await
this
.
provider
.
toCrossChainMessage
(
message
)
const
resolved
=
await
this
.
toCrossChainMessage
(
message
)
if
(
resolved
.
direction
===
MessageDirection
.
L2_TO_L1
)
{
if
(
resolved
.
direction
===
MessageDirection
.
L2_TO_L1
)
{
throw
new
Error
(
`cannot resend L2 to L1 message`
)
throw
new
Error
(
`cannot resend L2 to L1 message`
)
}
}
return
this
.
provider
.
contracts
.
l1
.
L1CrossDomainMessenger
.
populateTransaction
.
replayMessage
(
return
this
.
contracts
.
l1
.
L1CrossDomainMessenger
.
populateTransaction
.
replayMessage
(
resolved
.
target
,
resolved
.
target
,
resolved
.
sender
,
resolved
.
sender
,
resolved
.
message
,
resolved
.
message
,
...
@@ -195,13 +851,13 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -195,13 +851,13 @@ export class CrossChainMessenger implements ICrossChainMessenger {
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
TransactionRequest
>
=>
{
):
Promise
<
TransactionRequest
>
=>
{
const
resolved
=
await
this
.
provider
.
toCrossChainMessage
(
message
)
const
resolved
=
await
this
.
toCrossChainMessage
(
message
)
if
(
resolved
.
direction
===
MessageDirection
.
L1_TO_L2
)
{
if
(
resolved
.
direction
===
MessageDirection
.
L1_TO_L2
)
{
throw
new
Error
(
`cannot finalize L1 to L2 message`
)
throw
new
Error
(
`cannot finalize L1 to L2 message`
)
}
}
const
proof
=
await
this
.
provider
.
getMessageProof
(
resolved
)
const
proof
=
await
this
.
getMessageProof
(
resolved
)
return
this
.
provider
.
contracts
.
l1
.
L1CrossDomainMessenger
.
populateTransaction
.
relayMessage
(
return
this
.
contracts
.
l1
.
L1CrossDomainMessenger
.
populateTransaction
.
relayMessage
(
resolved
.
target
,
resolved
.
target
,
resolved
.
sender
,
resolved
.
sender
,
resolved
.
message
,
resolved
.
message
,
...
@@ -218,7 +874,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -218,7 +874,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
TransactionRequest
>
=>
{
):
Promise
<
TransactionRequest
>
=>
{
return
this
.
provider
.
bridges
.
ETH
.
populateTransaction
.
deposit
(
return
this
.
bridges
.
ETH
.
populateTransaction
.
deposit
(
ethers
.
constants
.
AddressZero
,
ethers
.
constants
.
AddressZero
,
predeploys
.
OVM_ETH
,
predeploys
.
OVM_ETH
,
amount
,
amount
,
...
@@ -232,7 +888,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -232,7 +888,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
TransactionRequest
>
=>
{
):
Promise
<
TransactionRequest
>
=>
{
return
this
.
provider
.
bridges
.
ETH
.
populateTransaction
.
withdraw
(
return
this
.
bridges
.
ETH
.
populateTransaction
.
withdraw
(
ethers
.
constants
.
AddressZero
,
ethers
.
constants
.
AddressZero
,
predeploys
.
OVM_ETH
,
predeploys
.
OVM_ETH
,
amount
,
amount
,
...
@@ -249,7 +905,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -249,7 +905,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
TransactionRequest
>
=>
{
):
Promise
<
TransactionRequest
>
=>
{
const
bridge
=
await
this
.
provider
.
getBridgeForTokenPair
(
l1Token
,
l2Token
)
const
bridge
=
await
this
.
getBridgeForTokenPair
(
l1Token
,
l2Token
)
return
bridge
.
populateTransaction
.
deposit
(
l1Token
,
l2Token
,
amount
,
opts
)
return
bridge
.
populateTransaction
.
deposit
(
l1Token
,
l2Token
,
amount
,
opts
)
},
},
...
@@ -261,7 +917,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -261,7 +917,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
TransactionRequest
>
=>
{
):
Promise
<
TransactionRequest
>
=>
{
const
bridge
=
await
this
.
provider
.
getBridgeForTokenPair
(
l1Token
,
l2Token
)
const
bridge
=
await
this
.
getBridgeForTokenPair
(
l1Token
,
l2Token
)
return
bridge
.
populateTransaction
.
withdraw
(
l1Token
,
l2Token
,
amount
,
opts
)
return
bridge
.
populateTransaction
.
withdraw
(
l1Token
,
l2Token
,
amount
,
opts
)
},
},
}
}
...
@@ -276,9 +932,9 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -276,9 +932,9 @@ export class CrossChainMessenger implements ICrossChainMessenger {
):
Promise
<
BigNumber
>
=>
{
):
Promise
<
BigNumber
>
=>
{
const
tx
=
await
this
.
populateTransaction
.
sendMessage
(
message
,
opts
)
const
tx
=
await
this
.
populateTransaction
.
sendMessage
(
message
,
opts
)
if
(
message
.
direction
===
MessageDirection
.
L1_TO_L2
)
{
if
(
message
.
direction
===
MessageDirection
.
L1_TO_L2
)
{
return
this
.
provider
.
l1Provider
.
estimateGas
(
tx
)
return
this
.
l1Provider
.
estimateGas
(
tx
)
}
else
{
}
else
{
return
this
.
provider
.
l2Provider
.
estimateGas
(
tx
)
return
this
.
l2Provider
.
estimateGas
(
tx
)
}
}
},
},
...
@@ -289,7 +945,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -289,7 +945,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
BigNumber
>
=>
{
):
Promise
<
BigNumber
>
=>
{
return
this
.
provider
.
l1Provider
.
estimateGas
(
return
this
.
l1Provider
.
estimateGas
(
await
this
.
populateTransaction
.
resendMessage
(
await
this
.
populateTransaction
.
resendMessage
(
message
,
message
,
messageGasLimit
,
messageGasLimit
,
...
@@ -304,7 +960,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -304,7 +960,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
BigNumber
>
=>
{
):
Promise
<
BigNumber
>
=>
{
return
this
.
provider
.
l1Provider
.
estimateGas
(
return
this
.
l1Provider
.
estimateGas
(
await
this
.
populateTransaction
.
finalizeMessage
(
message
,
opts
)
await
this
.
populateTransaction
.
finalizeMessage
(
message
,
opts
)
)
)
},
},
...
@@ -316,7 +972,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -316,7 +972,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
BigNumber
>
=>
{
):
Promise
<
BigNumber
>
=>
{
return
this
.
provider
.
l1Provider
.
estimateGas
(
return
this
.
l1Provider
.
estimateGas
(
await
this
.
populateTransaction
.
depositETH
(
amount
,
opts
)
await
this
.
populateTransaction
.
depositETH
(
amount
,
opts
)
)
)
},
},
...
@@ -327,7 +983,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -327,7 +983,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
BigNumber
>
=>
{
):
Promise
<
BigNumber
>
=>
{
return
this
.
provider
.
l2Provider
.
estimateGas
(
return
this
.
l2Provider
.
estimateGas
(
await
this
.
populateTransaction
.
withdrawETH
(
amount
,
opts
)
await
this
.
populateTransaction
.
withdrawETH
(
amount
,
opts
)
)
)
},
},
...
@@ -341,7 +997,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -341,7 +997,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
BigNumber
>
=>
{
):
Promise
<
BigNumber
>
=>
{
return
this
.
provider
.
l1Provider
.
estimateGas
(
return
this
.
l1Provider
.
estimateGas
(
await
this
.
populateTransaction
.
depositERC20
(
await
this
.
populateTransaction
.
depositERC20
(
l1Token
,
l1Token
,
l2Token
,
l2Token
,
...
@@ -359,7 +1015,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
...
@@ -359,7 +1015,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
BigNumber
>
=>
{
):
Promise
<
BigNumber
>
=>
{
return
this
.
provider
.
l2Provider
.
estimateGas
(
return
this
.
l2Provider
.
estimateGas
(
await
this
.
populateTransaction
.
withdrawERC20
(
await
this
.
populateTransaction
.
withdrawERC20
(
l1Token
,
l1Token
,
l2Token
,
l2Token
,
...
...
packages/sdk/src/cross-chain-provider.ts
deleted
100644 → 0
View file @
ceea12f3
/* eslint-disable @typescript-eslint/no-unused-vars */
import
{
Provider
,
BlockTag
,
TransactionReceipt
,
}
from
'
@ethersproject/abstract-provider
'
import
{
ethers
,
BigNumber
}
from
'
ethers
'
import
{
sleep
,
remove0x
}
from
'
@eth-optimism/core-utils
'
import
{
ICrossChainProvider
,
OEContracts
,
OEContractsLike
,
MessageLike
,
MessageRequestLike
,
TransactionLike
,
AddressLike
,
NumberLike
,
ProviderLike
,
CrossChainMessage
,
CrossChainMessageRequest
,
CrossChainMessageProof
,
MessageDirection
,
MessageStatus
,
TokenBridgeMessage
,
MessageReceipt
,
MessageReceiptStatus
,
BridgeAdapterData
,
BridgeAdapters
,
StateRoot
,
StateRootBatch
,
IBridgeAdapter
,
}
from
'
./interfaces
'
import
{
toProvider
,
toBigNumber
,
toTransactionHash
,
DeepPartial
,
getAllOEContracts
,
getBridgeAdapters
,
hashCrossChainMessage
,
makeMerkleTreeProof
,
makeStateTrieProof
,
encodeCrossChainMessage
,
}
from
'
./utils
'
export
class
CrossChainProvider
implements
ICrossChainProvider
{
public
l1Provider
:
Provider
public
l2Provider
:
Provider
public
l1ChainId
:
number
public
contracts
:
OEContracts
public
bridges
:
BridgeAdapters
/**
* Creates a new CrossChainProvider instance.
*
* @param opts Options for the provider.
* @param opts.l1Provider Provider for the L1 chain, or a JSON-RPC url.
* @param opts.l2Provider Provider for the L2 chain, or a JSON-RPC url.
* @param opts.l1ChainId Chain ID for the L1 chain.
* @param opts.contracts Optional contract address overrides.
* @param opts.bridges Optional bridge address list.
*/
constructor
(
opts
:
{
l1Provider
:
ProviderLike
l2Provider
:
ProviderLike
l1ChainId
:
NumberLike
contracts
?:
DeepPartial
<
OEContractsLike
>
bridges
?:
BridgeAdapterData
})
{
this
.
l1Provider
=
toProvider
(
opts
.
l1Provider
)
this
.
l2Provider
=
toProvider
(
opts
.
l2Provider
)
this
.
l1ChainId
=
toBigNumber
(
opts
.
l1ChainId
).
toNumber
()
this
.
contracts
=
getAllOEContracts
(
this
.
l1ChainId
,
{
l1SignerOrProvider
:
this
.
l1Provider
,
l2SignerOrProvider
:
this
.
l2Provider
,
overrides
:
opts
.
contracts
,
})
this
.
bridges
=
getBridgeAdapters
(
this
.
l1ChainId
,
this
,
{
overrides
:
opts
.
bridges
,
})
}
public
async
getMessagesByTransaction
(
transaction
:
TransactionLike
,
opts
:
{
direction
?:
MessageDirection
}
=
{}
):
Promise
<
CrossChainMessage
[]
>
{
const
txHash
=
toTransactionHash
(
transaction
)
let
receipt
:
TransactionReceipt
if
(
opts
.
direction
!==
undefined
)
{
// Get the receipt for the requested direction.
if
(
opts
.
direction
===
MessageDirection
.
L1_TO_L2
)
{
receipt
=
await
this
.
l1Provider
.
getTransactionReceipt
(
txHash
)
}
else
{
receipt
=
await
this
.
l2Provider
.
getTransactionReceipt
(
txHash
)
}
}
else
{
// Try both directions, starting with L1 => L2.
receipt
=
await
this
.
l1Provider
.
getTransactionReceipt
(
txHash
)
if
(
receipt
)
{
opts
.
direction
=
MessageDirection
.
L1_TO_L2
}
else
{
receipt
=
await
this
.
l2Provider
.
getTransactionReceipt
(
txHash
)
opts
.
direction
=
MessageDirection
.
L2_TO_L1
}
}
if
(
!
receipt
)
{
throw
new
Error
(
`unable to find transaction receipt for
${
txHash
}
`
)
}
// By this point opts.direction will always be defined.
const
messenger
=
opts
.
direction
===
MessageDirection
.
L1_TO_L2
?
this
.
contracts
.
l1
.
L1CrossDomainMessenger
:
this
.
contracts
.
l2
.
L2CrossDomainMessenger
return
receipt
.
logs
.
filter
((
log
)
=>
{
// Only look at logs emitted by the messenger address
return
log
.
address
===
messenger
.
address
})
.
filter
((
log
)
=>
{
// Only look at SentMessage logs specifically
const
parsed
=
messenger
.
interface
.
parseLog
(
log
)
return
parsed
.
name
===
'
SentMessage
'
})
.
map
((
log
)
=>
{
// Convert each SentMessage log into a message object
const
parsed
=
messenger
.
interface
.
parseLog
(
log
)
return
{
direction
:
opts
.
direction
,
target
:
parsed
.
args
.
target
,
sender
:
parsed
.
args
.
sender
,
message
:
parsed
.
args
.
message
,
messageNonce
:
parsed
.
args
.
messageNonce
,
gasLimit
:
parsed
.
args
.
gasLimit
,
logIndex
:
log
.
logIndex
,
blockNumber
:
log
.
blockNumber
,
transactionHash
:
log
.
transactionHash
,
}
})
}
public
async
getMessagesByAddress
(
address
:
AddressLike
,
opts
?:
{
direction
?:
MessageDirection
fromBlock
?:
NumberLike
toBlock
?:
NumberLike
}
):
Promise
<
CrossChainMessage
[]
>
{
throw
new
Error
(
'
Not implemented
'
)
}
public
async
getBridgeForTokenPair
(
l1Token
:
AddressLike
,
l2Token
:
AddressLike
):
Promise
<
IBridgeAdapter
>
{
const
bridges
:
IBridgeAdapter
[]
=
[]
for
(
const
bridge
of
Object
.
values
(
this
.
bridges
))
{
if
(
await
bridge
.
supportsTokenPair
(
l1Token
,
l2Token
))
{
bridges
.
push
(
bridge
)
}
}
if
(
bridges
.
length
===
0
)
{
throw
new
Error
(
`no supported bridge for token pair`
)
}
if
(
bridges
.
length
>
1
)
{
throw
new
Error
(
`found more than one bridge for token pair`
)
}
return
bridges
[
0
]
}
public
async
getTokenBridgeMessagesByAddress
(
address
:
AddressLike
,
opts
:
{
direction
?:
MessageDirection
}
=
{}
):
Promise
<
TokenBridgeMessage
[]
>
{
return
(
await
Promise
.
all
(
Object
.
values
(
this
.
bridges
).
map
(
async
(
bridge
)
=>
{
return
bridge
.
getTokenBridgeMessagesByAddress
(
address
,
opts
)
})
)
).
reduce
((
acc
,
val
)
=>
{
return
acc
.
concat
(
val
)
},
[])
}
public
async
getDepositsByAddress
(
address
:
AddressLike
,
opts
:
{
fromBlock
?:
BlockTag
toBlock
?:
BlockTag
}
=
{}
):
Promise
<
TokenBridgeMessage
[]
>
{
return
(
await
Promise
.
all
(
Object
.
values
(
this
.
bridges
).
map
(
async
(
bridge
)
=>
{
return
bridge
.
getDepositsByAddress
(
address
,
opts
)
})
)
).
reduce
((
acc
,
val
)
=>
{
return
acc
.
concat
(
val
)
},
[])
}
public
async
getWithdrawalsByAddress
(
address
:
AddressLike
,
opts
:
{
fromBlock
?:
BlockTag
toBlock
?:
BlockTag
}
=
{}
):
Promise
<
TokenBridgeMessage
[]
>
{
return
(
await
Promise
.
all
(
Object
.
values
(
this
.
bridges
).
map
(
async
(
bridge
)
=>
{
return
bridge
.
getWithdrawalsByAddress
(
address
,
opts
)
})
)
).
reduce
((
acc
,
val
)
=>
{
return
acc
.
concat
(
val
)
},
[])
}
public
async
toCrossChainMessage
(
message
:
MessageLike
):
Promise
<
CrossChainMessage
>
{
// TODO: Convert these checks into proper type checks.
if
((
message
as
CrossChainMessage
).
message
)
{
return
message
as
CrossChainMessage
}
else
if
(
(
message
as
TokenBridgeMessage
).
l1Token
&&
(
message
as
TokenBridgeMessage
).
l2Token
&&
(
message
as
TokenBridgeMessage
).
transactionHash
)
{
const
messages
=
await
this
.
getMessagesByTransaction
(
(
message
as
TokenBridgeMessage
).
transactionHash
)
// The `messages` object corresponds to a list of SentMessage events that were triggered by
// the same transaction. We want to find the specific SentMessage event that corresponds to
// the TokenBridgeMessage (either a ETHDepositInitiated, ERC20DepositInitiated, or
// WithdrawalInitiated event). We expect the behavior of bridge contracts to be that these
// TokenBridgeMessage events are triggered and then a SentMessage event is triggered. Our
// goal here is therefore to find the first SentMessage event that comes after the input
// event.
const
found
=
messages
.
sort
((
a
,
b
)
=>
{
// Sort all messages in ascending order by log index.
return
a
.
logIndex
-
b
.
logIndex
})
.
find
((
m
)
=>
{
return
m
.
logIndex
>
(
message
as
TokenBridgeMessage
).
logIndex
})
if
(
!
found
)
{
throw
new
Error
(
`could not find SentMessage event for message`
)
}
return
found
}
else
{
// TODO: Explicit TransactionLike check and throw if not TransactionLike
const
messages
=
await
this
.
getMessagesByTransaction
(
message
as
TransactionLike
)
// We only want to treat TransactionLike objects as MessageLike if they only emit a single
// message (very common). It's unintuitive to treat a TransactionLike as a MessageLike if
// they emit more than one message (which message do you pick?), so we throw an error.
if
(
messages
.
length
!==
1
)
{
throw
new
Error
(
`expected 1 message, got
${
messages
.
length
}
`
)
}
return
messages
[
0
]
}
}
public
async
getMessageStatus
(
message
:
MessageLike
):
Promise
<
MessageStatus
>
{
const
resolved
=
await
this
.
toCrossChainMessage
(
message
)
const
receipt
=
await
this
.
getMessageReceipt
(
resolved
)
if
(
resolved
.
direction
===
MessageDirection
.
L1_TO_L2
)
{
if
(
receipt
===
null
)
{
return
MessageStatus
.
UNCONFIRMED_L1_TO_L2_MESSAGE
}
else
{
if
(
receipt
.
receiptStatus
===
MessageReceiptStatus
.
RELAYED_SUCCEEDED
)
{
return
MessageStatus
.
RELAYED
}
else
{
return
MessageStatus
.
FAILED_L1_TO_L2_MESSAGE
}
}
}
else
{
if
(
receipt
===
null
)
{
const
stateRoot
=
await
this
.
getMessageStateRoot
(
resolved
)
if
(
stateRoot
===
null
)
{
return
MessageStatus
.
STATE_ROOT_NOT_PUBLISHED
}
else
{
const
challengePeriod
=
await
this
.
getChallengePeriodSeconds
()
const
targetBlock
=
await
this
.
l1Provider
.
getBlock
(
stateRoot
.
batch
.
blockNumber
)
const
latestBlock
=
await
this
.
l1Provider
.
getBlock
(
'
latest
'
)
if
(
targetBlock
.
timestamp
+
challengePeriod
>
latestBlock
.
timestamp
)
{
return
MessageStatus
.
IN_CHALLENGE_PERIOD
}
else
{
return
MessageStatus
.
READY_FOR_RELAY
}
}
}
else
{
if
(
receipt
.
receiptStatus
===
MessageReceiptStatus
.
RELAYED_SUCCEEDED
)
{
return
MessageStatus
.
RELAYED
}
else
{
return
MessageStatus
.
READY_FOR_RELAY
}
}
}
}
public
async
getMessageReceipt
(
message
:
MessageLike
):
Promise
<
MessageReceipt
>
{
const
resolved
=
await
this
.
toCrossChainMessage
(
message
)
const
messageHash
=
hashCrossChainMessage
(
resolved
)
// Here we want the messenger that will receive the message, not the one that sent it.
const
messenger
=
resolved
.
direction
===
MessageDirection
.
L1_TO_L2
?
this
.
contracts
.
l2
.
L2CrossDomainMessenger
:
this
.
contracts
.
l1
.
L1CrossDomainMessenger
const
relayedMessageEvents
=
await
messenger
.
queryFilter
(
messenger
.
filters
.
RelayedMessage
(
messageHash
)
)
// Great, we found the message. Convert it into a transaction receipt.
if
(
relayedMessageEvents
.
length
===
1
)
{
return
{
receiptStatus
:
MessageReceiptStatus
.
RELAYED_SUCCEEDED
,
transactionReceipt
:
await
relayedMessageEvents
[
0
].
getTransactionReceipt
(),
}
}
else
if
(
relayedMessageEvents
.
length
>
1
)
{
// Should never happen!
throw
new
Error
(
`multiple successful relays for message`
)
}
// We didn't find a transaction that relayed the message. We now attempt to find
// FailedRelayedMessage events instead.
const
failedRelayedMessageEvents
=
await
messenger
.
queryFilter
(
messenger
.
filters
.
FailedRelayedMessage
(
messageHash
)
)
// A transaction can fail to be relayed multiple times. We'll always return the last
// transaction that attempted to relay the message.
// TODO: Is this the best way to handle this?
if
(
failedRelayedMessageEvents
.
length
>
0
)
{
return
{
receiptStatus
:
MessageReceiptStatus
.
RELAYED_FAILED
,
transactionReceipt
:
await
failedRelayedMessageEvents
[
failedRelayedMessageEvents
.
length
-
1
].
getTransactionReceipt
(),
}
}
// TODO: If the user doesn't provide enough gas then there's a chance that FailedRelayedMessage
// will never be triggered. We should probably fix this at the contract level by requiring a
// minimum amount of input gas and designing the contracts such that the gas will always be
// enough to trigger the event. However, for now we need a temporary way to find L1 => L2
// transactions that fail but don't alert us because they didn't provide enough gas.
// TODO: Talk with the systems and protocol team about coordinating a hard fork that fixes this
// on both L1 and L2.
// Just return null if we didn't find a receipt. Slightly nicer than throwing an error.
return
null
}
public
async
waitForMessageReceipt
(
message
:
MessageLike
,
opts
:
{
confirmations
?:
number
pollIntervalMs
?:
number
timeoutMs
?:
number
}
=
{}
):
Promise
<
MessageReceipt
>
{
let
totalTimeMs
=
0
while
(
totalTimeMs
<
(
opts
.
timeoutMs
||
Infinity
))
{
const
tick
=
Date
.
now
()
const
receipt
=
await
this
.
getMessageReceipt
(
message
)
if
(
receipt
!==
null
)
{
return
receipt
}
else
{
await
sleep
(
opts
.
pollIntervalMs
||
4000
)
totalTimeMs
+=
Date
.
now
()
-
tick
}
}
throw
new
Error
(
`timed out waiting for message receipt`
)
}
public
async
estimateL2MessageGasLimit
(
message
:
MessageRequestLike
,
opts
?:
{
bufferPercent
?:
number
from
?:
string
}
):
Promise
<
BigNumber
>
{
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
)
{
throw
new
Error
(
`cannot estimate gas limit for L2 => L1 message`
)
}
const
estimate
=
await
this
.
l2Provider
.
estimateGas
({
from
,
to
:
resolved
.
target
,
data
:
resolved
.
message
,
})
// Return the estimate plus a buffer of 20% just in case.
const
bufferPercent
=
opts
?.
bufferPercent
||
20
return
estimate
.
mul
(
100
+
bufferPercent
).
div
(
100
)
}
public
async
estimateMessageWaitTimeSeconds
(
message
:
MessageLike
):
Promise
<
number
>
{
throw
new
Error
(
'
Not implemented
'
)
}
public
async
getChallengePeriodSeconds
():
Promise
<
number
>
{
const
challengePeriod
=
await
this
.
contracts
.
l1
.
StateCommitmentChain
.
FRAUD_PROOF_WINDOW
()
return
challengePeriod
.
toNumber
()
}
public
async
getMessageStateRoot
(
message
:
MessageLike
):
Promise
<
StateRoot
|
null
>
{
const
resolved
=
await
this
.
toCrossChainMessage
(
message
)
// State roots are only a thing for L2 to L1 messages.
if
(
resolved
.
direction
===
MessageDirection
.
L1_TO_L2
)
{
throw
new
Error
(
`cannot get a state root for an L1 to L2 message`
)
}
// We need the block number of the transaction that triggered the message so we can look up the
// state root batch that corresponds to that block number.
const
messageTxReceipt
=
await
this
.
l2Provider
.
getTransactionReceipt
(
resolved
.
transactionHash
)
// Every block has exactly one transaction in it. Since there's a genesis block, the
// transaction index will always be one less than the block number.
const
messageTxIndex
=
messageTxReceipt
.
blockNumber
-
1
// Pull down the state root batch, we'll try to pick out the specific state root that
// corresponds to our message.
const
stateRootBatch
=
await
this
.
getStateRootBatchByTransactionIndex
(
messageTxIndex
)
// No state root batch, no state root.
if
(
stateRootBatch
===
null
)
{
return
null
}
// We have a state root batch, now we need to find the specific state root for our transaction.
// First we need to figure out the index of the state root within the batch we found. This is
// going to be the original transaction index offset by the total number of previous state
// roots.
const
indexInBatch
=
messageTxIndex
-
stateRootBatch
.
header
.
prevTotalElements
.
toNumber
()
// Just a sanity check.
if
(
stateRootBatch
.
stateRoots
.
length
<=
indexInBatch
)
{
// Should never happen!
throw
new
Error
(
`state root does not exist in batch`
)
}
return
{
stateRoot
:
stateRootBatch
.
stateRoots
[
indexInBatch
],
stateRootIndexInBatch
:
indexInBatch
,
batch
:
stateRootBatch
,
}
}
public
async
getStateBatchAppendedEventByBatchIndex
(
batchIndex
:
number
):
Promise
<
ethers
.
Event
|
null
>
{
const
events
=
await
this
.
contracts
.
l1
.
StateCommitmentChain
.
queryFilter
(
this
.
contracts
.
l1
.
StateCommitmentChain
.
filters
.
StateBatchAppended
(
batchIndex
)
)
if
(
events
.
length
===
0
)
{
return
null
}
else
if
(
events
.
length
>
1
)
{
// Should never happen!
throw
new
Error
(
`found more than one StateBatchAppended event`
)
}
else
{
return
events
[
0
]
}
}
public
async
getStateBatchAppendedEventByTransactionIndex
(
transactionIndex
:
number
):
Promise
<
ethers
.
Event
|
null
>
{
const
isEventHi
=
(
event
:
ethers
.
Event
,
index
:
number
)
=>
{
const
prevTotalElements
=
event
.
args
.
_prevTotalElements
.
toNumber
()
return
index
<
prevTotalElements
}
const
isEventLo
=
(
event
:
ethers
.
Event
,
index
:
number
)
=>
{
const
prevTotalElements
=
event
.
args
.
_prevTotalElements
.
toNumber
()
const
batchSize
=
event
.
args
.
_batchSize
.
toNumber
()
return
index
>=
prevTotalElements
+
batchSize
}
const
totalBatches
:
ethers
.
BigNumber
=
await
this
.
contracts
.
l1
.
StateCommitmentChain
.
getTotalBatches
()
if
(
totalBatches
.
eq
(
0
))
{
return
null
}
let
lowerBound
=
0
let
upperBound
=
totalBatches
.
toNumber
()
-
1
let
batchEvent
:
ethers
.
Event
|
null
=
await
this
.
getStateBatchAppendedEventByBatchIndex
(
upperBound
)
if
(
isEventLo
(
batchEvent
,
transactionIndex
))
{
// Upper bound is too low, means this transaction doesn't have a corresponding state batch yet.
return
null
}
else
if
(
!
isEventHi
(
batchEvent
,
transactionIndex
))
{
// Upper bound is not too low and also not too high. This means the upper bound event is the
// one we're looking for! Return it.
return
batchEvent
}
// Binary search to find the right event. The above checks will guarantee that the event does
// exist and that we'll find it during this search.
while
(
lowerBound
<
upperBound
)
{
const
middleOfBounds
=
Math
.
floor
((
lowerBound
+
upperBound
)
/
2
)
batchEvent
=
await
this
.
getStateBatchAppendedEventByBatchIndex
(
middleOfBounds
)
if
(
isEventHi
(
batchEvent
,
transactionIndex
))
{
upperBound
=
middleOfBounds
}
else
if
(
isEventLo
(
batchEvent
,
transactionIndex
))
{
lowerBound
=
middleOfBounds
}
else
{
break
}
}
return
batchEvent
}
public
async
getStateRootBatchByTransactionIndex
(
transactionIndex
:
number
):
Promise
<
StateRootBatch
|
null
>
{
const
stateBatchAppendedEvent
=
await
this
.
getStateBatchAppendedEventByTransactionIndex
(
transactionIndex
)
if
(
stateBatchAppendedEvent
===
null
)
{
return
null
}
const
stateBatchTransaction
=
await
stateBatchAppendedEvent
.
getTransaction
()
const
[
stateRoots
]
=
this
.
contracts
.
l1
.
StateCommitmentChain
.
interface
.
decodeFunctionData
(
'
appendStateBatch
'
,
stateBatchTransaction
.
data
)
return
{
blockNumber
:
stateBatchAppendedEvent
.
blockNumber
,
stateRoots
,
header
:
{
batchIndex
:
stateBatchAppendedEvent
.
args
.
_batchIndex
,
batchRoot
:
stateBatchAppendedEvent
.
args
.
_batchRoot
,
batchSize
:
stateBatchAppendedEvent
.
args
.
_batchSize
,
prevTotalElements
:
stateBatchAppendedEvent
.
args
.
_prevTotalElements
,
extraData
:
stateBatchAppendedEvent
.
args
.
_extraData
,
},
}
}
public
async
getMessageProof
(
message
:
MessageLike
):
Promise
<
CrossChainMessageProof
>
{
const
resolved
=
await
this
.
toCrossChainMessage
(
message
)
if
(
resolved
.
direction
===
MessageDirection
.
L1_TO_L2
)
{
throw
new
Error
(
`can only generate proofs for L2 to L1 messages`
)
}
const
stateRoot
=
await
this
.
getMessageStateRoot
(
resolved
)
if
(
stateRoot
===
null
)
{
throw
new
Error
(
`state root for message not yet published`
)
}
// We need to calculate the specific storage slot that demonstrates that this message was
// actually included in the L2 chain. The following calculation is based on the fact that
// messages are stored in the following mapping on L2:
// https://github.com/ethereum-optimism/optimism/blob/c84d3450225306abbb39b4e7d6d82424341df2be/packages/contracts/contracts/L2/predeploys/OVM_L2ToL1MessagePasser.sol#L23
// You can read more about how Solidity storage slots are computed for mappings here:
// https://docs.soliditylang.org/en/v0.8.4/internals/layout_in_storage.html#mappings-and-dynamic-arrays
const
messageSlot
=
ethers
.
utils
.
keccak256
(
ethers
.
utils
.
keccak256
(
encodeCrossChainMessage
(
resolved
)
+
remove0x
(
this
.
contracts
.
l2
.
L2CrossDomainMessenger
.
address
)
)
+
'
00
'
.
repeat
(
32
)
)
const
stateTrieProof
=
await
makeStateTrieProof
(
this
.
l2Provider
as
any
,
resolved
.
blockNumber
,
this
.
contracts
.
l2
.
OVM_L2ToL1MessagePasser
.
address
,
messageSlot
)
return
{
stateRoot
:
stateRoot
.
stateRoot
,
stateRootBatchHeader
:
stateRoot
.
batch
.
header
,
stateRootProof
:
{
index
:
stateRoot
.
stateRootIndexInBatch
,
siblings
:
makeMerkleTreeProof
(
stateRoot
.
batch
.
stateRoots
,
stateRoot
.
stateRootIndexInBatch
),
},
stateTrieWitness
:
stateTrieProof
.
accountProof
,
storageTrieWitness
:
stateTrieProof
.
storageProof
,
}
}
}
packages/sdk/src/index.ts
View file @
77bd8dfc
export
*
from
'
./interfaces
'
export
*
from
'
./interfaces
'
export
*
from
'
./utils
'
export
*
from
'
./utils
'
export
*
from
'
./cross-chain-provider
'
export
*
from
'
./cross-chain-messenger
'
export
*
from
'
./cross-chain-messenger
'
export
*
from
'
./adapters
'
export
*
from
'
./adapters
'
packages/sdk/src/interfaces/bridge-adapter.ts
View file @
77bd8dfc
...
@@ -11,7 +11,7 @@ import {
...
@@ -11,7 +11,7 @@ import {
MessageDirection
,
MessageDirection
,
TokenBridgeMessage
,
TokenBridgeMessage
,
}
from
'
./types
'
}
from
'
./types
'
import
{
ICrossChain
Provider
}
from
'
./cross-chain-provid
er
'
import
{
ICrossChain
Messenger
}
from
'
./cross-chain-messeng
er
'
/**
/**
* Represents an adapter for an L1<>L2 token bridge. Each custom bridge currently needs its own
* Represents an adapter for an L1<>L2 token bridge. Each custom bridge currently needs its own
...
@@ -21,7 +21,7 @@ export interface IBridgeAdapter {
...
@@ -21,7 +21,7 @@ export interface IBridgeAdapter {
/**
/**
* Provider used to make queries related to cross-chain interactions.
* Provider used to make queries related to cross-chain interactions.
*/
*/
provider
:
ICrossChainProvid
er
messenger
:
ICrossChainMesseng
er
/**
/**
* L1 bridge contract.
* L1 bridge contract.
...
...
packages/sdk/src/interfaces/cross-chain-messenger.ts
View file @
77bd8dfc
import
{
Overrides
,
Signer
,
BigNumber
}
from
'
ethers
'
import
{
Event
,
BigNumber
,
Overrides
}
from
'
ethers
'
import
{
import
{
Provider
,
BlockTag
,
TransactionRequest
,
TransactionRequest
,
TransactionResponse
,
TransactionResponse
,
}
from
'
@ethersproject/abstract-provider
'
}
from
'
@ethersproject/abstract-provider
'
import
{
Signer
}
from
'
@ethersproject/abstract-signer
'
import
{
import
{
MessageLike
,
MessageLike
,
MessageRequestLike
,
TransactionLike
,
AddressLike
,
NumberLike
,
NumberLike
,
CrossChainMessage
,
CrossChainMessageRequest
,
CrossChainMessageRequest
,
AddressLike
,
CrossChainMessageProof
,
MessageDirection
,
MessageStatus
,
TokenBridgeMessage
,
OEContracts
,
MessageReceipt
,
StateRoot
,
StateRootBatch
,
BridgeAdapters
,
}
from
'
./types
'
}
from
'
./types
'
import
{
I
CrossChainProvider
}
from
'
./cross-chain-provid
er
'
import
{
I
BridgeAdapter
}
from
'
./bridge-adapt
er
'
/**
/**
*
Represents a utility class for making L1/L2 cross-chain trans
actions.
*
Handles L1/L2 inter
actions.
*/
*/
export
interface
ICrossChainMessenger
{
export
interface
ICrossChainMessenger
{
/**
/**
* Provider that will be used to interact with the L1/L2 system.
* Provider connected to the L1 chain.
*/
l1SignerOrProvider
:
Signer
|
Provider
/**
* Provider connected to the L2 chain.
*/
l2SignerOrProvider
:
Signer
|
Provider
/**
* Chain ID for the L1 network.
*/
l1ChainId
:
number
/**
* Contract objects attached to their respective providers and addresses.
*/
contracts
:
OEContracts
/**
* List of custom bridges for the given network.
*/
*/
provider
:
ICrossChainProvider
bridges
:
BridgeAdapters
/**
/**
* Signer that will carry out L1 transactions.
* Provider connected to the L1 chain.
*/
l1Provider
:
Provider
/**
* Provider connected to the L2 chain.
*/
l2Provider
:
Provider
/**
* Signer connected to the L1 chain.
*/
*/
l1Signer
:
Signer
l1Signer
:
Signer
/**
/**
* Signer
that will carry out L2 transactions
.
* Signer
connected to the L2 chain
.
*/
*/
l2Signer
:
Signer
l2Signer
:
Signer
/**
* Retrieves all cross chain messages sent within a given transaction.
*
* @param transaction Transaction hash or receipt to find messages from.
* @param opts Options object.
* @param opts.direction Direction to search for messages in. If not provided, will attempt to
* automatically search both directions under the assumption that a transaction hash will only
* exist on one chain. If the hash exists on both chains, will throw an error.
* @returns All cross chain messages sent within the transaction.
*/
getMessagesByTransaction
(
transaction
:
TransactionLike
,
opts
?:
{
direction
?:
MessageDirection
}
):
Promise
<
CrossChainMessage
[]
>
/**
* Retrieves all cross chain messages sent by a particular address.
*
* @param address Address to search for messages from.
* @param opts Options object.
* @param opts.direction Direction to search for messages in. If not provided, will attempt to
* find all messages in both directions.
* @param opts.fromBlock Block to start searching for messages from. If not provided, will start
* from the first block (block #0).
* @param opts.toBlock Block to stop searching for messages at. If not provided, will stop at the
* latest known block ("latest").
* @returns All cross chain messages sent by the particular address.
*/
getMessagesByAddress
(
address
:
AddressLike
,
opts
?:
{
direction
?:
MessageDirection
fromBlock
?:
NumberLike
toBlock
?:
NumberLike
}
):
Promise
<
CrossChainMessage
[]
>
/**
* Finds the appropriate bridge adapter for a given L1<>L2 token pair. Will throw if no bridges
* support the token pair or if more than one bridge supports the token pair.
*
* @param l1Token L1 token address.
* @param l2Token L2 token address.
* @returns The appropriate bridge adapter for the given token pair.
*/
getBridgeForTokenPair
(
l1Token
:
AddressLike
,
l2Token
:
AddressLike
):
Promise
<
IBridgeAdapter
>
/**
* Finds all cross chain messages that correspond to token deposits or withdrawals sent by a
* particular address. Useful for finding deposits/withdrawals because the sender of the message
* will appear to be the StandardBridge contract and not the actual end user.
*
* @param address Address to search for messages from.
* @param opts Options object.
* @param opts.direction Direction to search for messages in. If not provided, will attempt to
* find all messages in both directions.
* @returns All token bridge messages sent by the given address.
*/
getTokenBridgeMessagesByAddress
(
address
:
AddressLike
,
opts
?:
{
direction
?:
MessageDirection
fromBlock
?:
BlockTag
toBlock
?:
BlockTag
}
):
Promise
<
TokenBridgeMessage
[]
>
/**
* Alias for getTokenBridgeMessagesByAddress with a drection of L1_TO_L2.
*
* @param address Address to search for messages from.
* @param opts Options object.
* @param opts.fromBlock Block to start searching for messages from. If not provided, will start
* from the first block (block #0).
* @param opts.toBlock Block to stop searching for messages at. If not provided, will stop at the
* latest known block ("latest").
* @returns All deposit token bridge messages sent by the given address.
*/
getDepositsByAddress
(
address
:
AddressLike
,
opts
?:
{
fromBlock
?:
BlockTag
toBlock
?:
BlockTag
}
):
Promise
<
TokenBridgeMessage
[]
>
/**
* Alias for getTokenBridgeMessagesByAddress with a drection of L2_TO_L1.
*
* @param address Address to search for messages from.
* @param opts Options object.
* @param opts.fromBlock Block to start searching for messages from. If not provided, will start
* from the first block (block #0).
* @param opts.toBlock Block to stop searching for messages at. If not provided, will stop at the
* latest known block ("latest").
* @returns All withdrawal token bridge messages sent by the given address.
*/
getWithdrawalsByAddress
(
address
:
AddressLike
,
opts
?:
{
fromBlock
?:
BlockTag
toBlock
?:
BlockTag
}
):
Promise
<
TokenBridgeMessage
[]
>
/**
* Resolves a MessageLike into a CrossChainMessage object.
* Unlike other coercion functions, this function is stateful and requires making additional
* requests. For now I'm going to keep this function here, but we could consider putting a
* similar function inside of utils/coercion.ts if people want to use this without having to
* create an entire CrossChainProvider object.
*
* @param message MessageLike to resolve into a CrossChainMessage.
* @returns Message coerced into a CrossChainMessage.
*/
toCrossChainMessage
(
message
:
MessageLike
):
Promise
<
CrossChainMessage
>
/**
* Retrieves the status of a particular message as an enum.
*
* @param message Cross chain message to check the status of.
* @returns Status of the message.
*/
getMessageStatus
(
message
:
MessageLike
):
Promise
<
MessageStatus
>
/**
* Finds the receipt of the transaction that executed a particular cross chain message.
*
* @param message Message to find the receipt of.
* @returns CrossChainMessage receipt including receipt of the transaction that relayed the
* given message.
*/
getMessageReceipt
(
message
:
MessageLike
):
Promise
<
MessageReceipt
>
/**
* Waits for a message to be executed and returns the receipt of the transaction that executed
* the given message.
*
* @param message Message to wait for.
* @param opts Options to pass to the waiting function.
* @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.
*/
waitForMessageReceipt
(
message
:
MessageLike
,
opts
?:
{
confirmations
?:
number
pollIntervalMs
?:
number
timeoutMs
?:
number
}
):
Promise
<
MessageReceipt
>
/**
* Estimates the amount of gas required to fully execute a given message on L2. Only applies to
* L1 => L2 messages. You would supply this gas limit when sending the message to L2.
*
* @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
:
MessageRequestLike
,
opts
?:
{
bufferPercent
?:
number
from
?:
string
}
):
Promise
<
BigNumber
>
/**
* Returns the estimated amount of time before the message can be executed. When this is a
* message being sent to L1, this will return the estimated time until the message will complete
* its challenge period. When this is a message being sent to L2, this will return the estimated
* amount of time until the message will be picked up and executed on L2.
*
* @param message Message to estimate the time remaining for.
* @returns Estimated amount of time remaining (in seconds) before the message can be executed.
*/
estimateMessageWaitTimeSeconds
(
message
:
MessageLike
):
Promise
<
number
>
/**
* Queries the current challenge period in seconds from the StateCommitmentChain.
*
* @returns Current challenge period in seconds.
*/
getChallengePeriodSeconds
():
Promise
<
number
>
/**
* Returns the state root that corresponds to a given message. This is the state root for the
* block in which the transaction was included, as published to the StateCommitmentChain. If the
* state root for the given message has not been published yet, this function returns null.
*
* @param message Message to find a state root for.
* @returns State root for the block in which the message was created.
*/
getMessageStateRoot
(
message
:
MessageLike
):
Promise
<
StateRoot
|
null
>
/**
* Returns the StateBatchAppended event that was emitted when the batch with a given index was
* created. Returns null if no such event exists (the batch has not been submitted).
*
* @param batchIndex Index of the batch to find an event for.
* @returns StateBatchAppended event for the batch, or null if no such batch exists.
*/
getStateBatchAppendedEventByBatchIndex
(
batchIndex
:
number
):
Promise
<
Event
|
null
>
/**
* Returns the StateBatchAppended event for the batch that includes the transaction with the
* given index. Returns null if no such event exists.
*
* @param transactionIndex Index of the L2 transaction to find an event for.
* @returns StateBatchAppended event for the batch that includes the given transaction by index.
*/
getStateBatchAppendedEventByTransactionIndex
(
transactionIndex
:
number
):
Promise
<
Event
|
null
>
/**
* Returns information about the state root batch that included the state root for the given
* transaction by index. Returns null if no such state root has been published yet.
*
* @param transactionIndex Index of the L2 transaction to find a state root batch for.
* @returns State root batch for the given transaction index, or null if none exists yet.
*/
getStateRootBatchByTransactionIndex
(
transactionIndex
:
number
):
Promise
<
StateRootBatch
|
null
>
/**
* Generates the proof required to finalize an L2 to L1 message.
*
* @param message Message to generate a proof for.
* @returns Proof that can be used to finalize the message.
*/
getMessageProof
(
message
:
MessageLike
):
Promise
<
CrossChainMessageProof
>
/**
/**
* Sends a given cross chain message. Where the message is sent depends on the direction attached
* Sends a given cross chain message. Where the message is sent depends on the direction attached
* to the message itself.
* to the message itself.
*
*
* @param message Cross chain message to send.
* @param message Cross chain message to send.
* @param opts Additional options.
* @param opts Additional options.
* @param opts.signer Optional signer to use to send the transaction.
* @param opts.l2GasLimit Optional gas limit to use for the transaction on L2.
* @param opts.l2GasLimit Optional gas limit to use for the transaction on L2.
* @param opts.overrides Optional transaction overrides.
* @param opts.overrides Optional transaction overrides.
* @returns Transaction response for the message sending transaction.
* @returns Transaction response for the message sending transaction.
...
@@ -44,6 +337,7 @@ export interface ICrossChainMessenger {
...
@@ -44,6 +337,7 @@ export interface ICrossChainMessenger {
sendMessage
(
sendMessage
(
message
:
CrossChainMessageRequest
,
message
:
CrossChainMessageRequest
,
opts
?:
{
opts
?:
{
signer
?:
Signer
l2GasLimit
?:
NumberLike
l2GasLimit
?:
NumberLike
overrides
?:
Overrides
overrides
?:
Overrides
}
}
...
@@ -56,6 +350,7 @@ export interface ICrossChainMessenger {
...
@@ -56,6 +350,7 @@ export interface ICrossChainMessenger {
* @param message Cross chain message to resend.
* @param message Cross chain message to resend.
* @param messageGasLimit New gas limit to use for the message.
* @param messageGasLimit New gas limit to use for the message.
* @param opts Additional options.
* @param opts Additional options.
* @param opts.signer Optional signer to use to send the transaction.
* @param opts.overrides Optional transaction overrides.
* @param opts.overrides Optional transaction overrides.
* @returns Transaction response for the message resending transaction.
* @returns Transaction response for the message resending transaction.
*/
*/
...
@@ -63,6 +358,7 @@ export interface ICrossChainMessenger {
...
@@ -63,6 +358,7 @@ export interface ICrossChainMessenger {
message
:
MessageLike
,
message
:
MessageLike
,
messageGasLimit
:
NumberLike
,
messageGasLimit
:
NumberLike
,
opts
?:
{
opts
?:
{
signer
?:
Signer
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
TransactionResponse
>
):
Promise
<
TransactionResponse
>
...
@@ -73,12 +369,14 @@ export interface ICrossChainMessenger {
...
@@ -73,12 +369,14 @@ export interface ICrossChainMessenger {
*
*
* @param message Message to finalize.
* @param message Message to finalize.
* @param opts Additional options.
* @param opts Additional options.
* @param opts.signer Optional signer to use to send the transaction.
* @param opts.overrides Optional transaction overrides.
* @param opts.overrides Optional transaction overrides.
* @returns Transaction response for the finalization transaction.
* @returns Transaction response for the finalization transaction.
*/
*/
finalizeMessage
(
finalizeMessage
(
message
:
MessageLike
,
message
:
MessageLike
,
opts
?:
{
opts
?:
{
signer
?:
Signer
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
TransactionResponse
>
):
Promise
<
TransactionResponse
>
...
@@ -88,6 +386,7 @@ export interface ICrossChainMessenger {
...
@@ -88,6 +386,7 @@ export interface ICrossChainMessenger {
*
*
* @param amount Amount of ETH to deposit (in wei).
* @param amount Amount of ETH to deposit (in wei).
* @param opts Additional options.
* @param opts Additional options.
* @param opts.signer Optional signer to use to send the transaction.
* @param opts.l2GasLimit Optional gas limit to use for the transaction on L2.
* @param opts.l2GasLimit Optional gas limit to use for the transaction on L2.
* @param opts.overrides Optional transaction overrides.
* @param opts.overrides Optional transaction overrides.
* @returns Transaction response for the deposit transaction.
* @returns Transaction response for the deposit transaction.
...
@@ -95,6 +394,7 @@ export interface ICrossChainMessenger {
...
@@ -95,6 +394,7 @@ export interface ICrossChainMessenger {
depositETH
(
depositETH
(
amount
:
NumberLike
,
amount
:
NumberLike
,
opts
?:
{
opts
?:
{
signer
?:
Signer
l2GasLimit
?:
NumberLike
l2GasLimit
?:
NumberLike
overrides
?:
Overrides
overrides
?:
Overrides
}
}
...
@@ -105,12 +405,14 @@ export interface ICrossChainMessenger {
...
@@ -105,12 +405,14 @@ export interface ICrossChainMessenger {
*
*
* @param amount Amount of ETH to withdraw.
* @param amount Amount of ETH to withdraw.
* @param opts Additional options.
* @param opts Additional options.
* @param opts.signer Optional signer to use to send the transaction.
* @param opts.overrides Optional transaction overrides.
* @param opts.overrides Optional transaction overrides.
* @returns Transaction response for the withdraw transaction.
* @returns Transaction response for the withdraw transaction.
*/
*/
withdrawETH
(
withdrawETH
(
amount
:
NumberLike
,
amount
:
NumberLike
,
opts
?:
{
opts
?:
{
signer
?:
Signer
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
TransactionResponse
>
):
Promise
<
TransactionResponse
>
...
@@ -122,6 +424,7 @@ export interface ICrossChainMessenger {
...
@@ -122,6 +424,7 @@ export interface ICrossChainMessenger {
* @param l2Token Address of the L2 token.
* @param l2Token Address of the L2 token.
* @param amount Amount to deposit.
* @param amount Amount to deposit.
* @param opts Additional options.
* @param opts Additional options.
* @param opts.signer Optional signer to use to send the transaction.
* @param opts.l2GasLimit Optional gas limit to use for the transaction on L2.
* @param opts.l2GasLimit Optional gas limit to use for the transaction on L2.
* @param opts.overrides Optional transaction overrides.
* @param opts.overrides Optional transaction overrides.
* @returns Transaction response for the deposit transaction.
* @returns Transaction response for the deposit transaction.
...
@@ -131,6 +434,7 @@ export interface ICrossChainMessenger {
...
@@ -131,6 +434,7 @@ export interface ICrossChainMessenger {
l2Token
:
AddressLike
,
l2Token
:
AddressLike
,
amount
:
NumberLike
,
amount
:
NumberLike
,
opts
?:
{
opts
?:
{
signer
?:
Signer
l2GasLimit
?:
NumberLike
l2GasLimit
?:
NumberLike
overrides
?:
Overrides
overrides
?:
Overrides
}
}
...
@@ -143,6 +447,7 @@ export interface ICrossChainMessenger {
...
@@ -143,6 +447,7 @@ export interface ICrossChainMessenger {
* @param l2Token Address of the L2 token.
* @param l2Token Address of the L2 token.
* @param amount Amount to withdraw.
* @param amount Amount to withdraw.
* @param opts Additional options.
* @param opts Additional options.
* @param opts.signer Optional signer to use to send the transaction.
* @param opts.overrides Optional transaction overrides.
* @param opts.overrides Optional transaction overrides.
* @returns Transaction response for the withdraw transaction.
* @returns Transaction response for the withdraw transaction.
*/
*/
...
@@ -151,6 +456,7 @@ export interface ICrossChainMessenger {
...
@@ -151,6 +456,7 @@ export interface ICrossChainMessenger {
l2Token
:
AddressLike
,
l2Token
:
AddressLike
,
amount
:
NumberLike
,
amount
:
NumberLike
,
opts
?:
{
opts
?:
{
signer
?:
Signer
overrides
?:
Overrides
overrides
?:
Overrides
}
}
):
Promise
<
TransactionResponse
>
):
Promise
<
TransactionResponse
>
...
...
packages/sdk/src/interfaces/cross-chain-provider.ts
deleted
100644 → 0
View file @
ceea12f3
import
{
Event
,
BigNumber
}
from
'
ethers
'
import
{
Provider
,
BlockTag
}
from
'
@ethersproject/abstract-provider
'
import
{
MessageLike
,
MessageRequestLike
,
TransactionLike
,
AddressLike
,
NumberLike
,
CrossChainMessage
,
CrossChainMessageProof
,
MessageDirection
,
MessageStatus
,
TokenBridgeMessage
,
OEContracts
,
MessageReceipt
,
StateRoot
,
StateRootBatch
,
BridgeAdapters
,
}
from
'
./types
'
import
{
IBridgeAdapter
}
from
'
./bridge-adapter
'
/**
* Represents the L1/L2 connection. Only handles read requests. If you want to send messages, use
* the CrossChainMessenger contract which takes a CrossChainProvider and a signer as inputs.
*/
export
interface
ICrossChainProvider
{
/**
* Provider connected to the L1 chain.
*/
l1Provider
:
Provider
/**
* Provider connected to the L2 chain.
*/
l2Provider
:
Provider
/**
* Chain ID for the L1 network.
*/
l1ChainId
:
number
/**
* Contract objects attached to their respective providers and addresses.
*/
contracts
:
OEContracts
/**
* List of custom bridges for the given network.
*/
bridges
:
BridgeAdapters
/**
* Retrieves all cross chain messages sent within a given transaction.
*
* @param transaction Transaction hash or receipt to find messages from.
* @param opts Options object.
* @param opts.direction Direction to search for messages in. If not provided, will attempt to
* automatically search both directions under the assumption that a transaction hash will only
* exist on one chain. If the hash exists on both chains, will throw an error.
* @returns All cross chain messages sent within the transaction.
*/
getMessagesByTransaction
(
transaction
:
TransactionLike
,
opts
?:
{
direction
?:
MessageDirection
}
):
Promise
<
CrossChainMessage
[]
>
/**
* Retrieves all cross chain messages sent by a particular address.
*
* @param address Address to search for messages from.
* @param opts Options object.
* @param opts.direction Direction to search for messages in. If not provided, will attempt to
* find all messages in both directions.
* @param opts.fromBlock Block to start searching for messages from. If not provided, will start
* from the first block (block #0).
* @param opts.toBlock Block to stop searching for messages at. If not provided, will stop at the
* latest known block ("latest").
* @returns All cross chain messages sent by the particular address.
*/
getMessagesByAddress
(
address
:
AddressLike
,
opts
?:
{
direction
?:
MessageDirection
fromBlock
?:
NumberLike
toBlock
?:
NumberLike
}
):
Promise
<
CrossChainMessage
[]
>
/**
* Finds the appropriate bridge adapter for a given L1<>L2 token pair. Will throw if no bridges
* support the token pair or if more than one bridge supports the token pair.
*
* @param l1Token L1 token address.
* @param l2Token L2 token address.
* @returns The appropriate bridge adapter for the given token pair.
*/
getBridgeForTokenPair
(
l1Token
:
AddressLike
,
l2Token
:
AddressLike
):
Promise
<
IBridgeAdapter
>
/**
* Finds all cross chain messages that correspond to token deposits or withdrawals sent by a
* particular address. Useful for finding deposits/withdrawals because the sender of the message
* will appear to be the StandardBridge contract and not the actual end user.
*
* @param address Address to search for messages from.
* @param opts Options object.
* @param opts.direction Direction to search for messages in. If not provided, will attempt to
* find all messages in both directions.
* @returns All token bridge messages sent by the given address.
*/
getTokenBridgeMessagesByAddress
(
address
:
AddressLike
,
opts
?:
{
direction
?:
MessageDirection
fromBlock
?:
BlockTag
toBlock
?:
BlockTag
}
):
Promise
<
TokenBridgeMessage
[]
>
/**
* Alias for getTokenBridgeMessagesByAddress with a drection of L1_TO_L2.
*
* @param address Address to search for messages from.
* @param opts Options object.
* @param opts.fromBlock Block to start searching for messages from. If not provided, will start
* from the first block (block #0).
* @param opts.toBlock Block to stop searching for messages at. If not provided, will stop at the
* latest known block ("latest").
* @returns All deposit token bridge messages sent by the given address.
*/
getDepositsByAddress
(
address
:
AddressLike
,
opts
?:
{
fromBlock
?:
BlockTag
toBlock
?:
BlockTag
}
):
Promise
<
TokenBridgeMessage
[]
>
/**
* Alias for getTokenBridgeMessagesByAddress with a drection of L2_TO_L1.
*
* @param address Address to search for messages from.
* @param opts Options object.
* @param opts.fromBlock Block to start searching for messages from. If not provided, will start
* from the first block (block #0).
* @param opts.toBlock Block to stop searching for messages at. If not provided, will stop at the
* latest known block ("latest").
* @returns All withdrawal token bridge messages sent by the given address.
*/
getWithdrawalsByAddress
(
address
:
AddressLike
,
opts
?:
{
fromBlock
?:
BlockTag
toBlock
?:
BlockTag
}
):
Promise
<
TokenBridgeMessage
[]
>
/**
* Resolves a MessageLike into a CrossChainMessage object.
* Unlike other coercion functions, this function is stateful and requires making additional
* requests. For now I'm going to keep this function here, but we could consider putting a
* similar function inside of utils/coercion.ts if people want to use this without having to
* create an entire CrossChainProvider object.
*
* @param message MessageLike to resolve into a CrossChainMessage.
* @returns Message coerced into a CrossChainMessage.
*/
toCrossChainMessage
(
message
:
MessageLike
):
Promise
<
CrossChainMessage
>
/**
* Retrieves the status of a particular message as an enum.
*
* @param message Cross chain message to check the status of.
* @returns Status of the message.
*/
getMessageStatus
(
message
:
MessageLike
):
Promise
<
MessageStatus
>
/**
* Finds the receipt of the transaction that executed a particular cross chain message.
*
* @param message Message to find the receipt of.
* @returns CrossChainMessage receipt including receipt of the transaction that relayed the
* given message.
*/
getMessageReceipt
(
message
:
MessageLike
):
Promise
<
MessageReceipt
>
/**
* Waits for a message to be executed and returns the receipt of the transaction that executed
* the given message.
*
* @param message Message to wait for.
* @param opts Options to pass to the waiting function.
* @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.
*/
waitForMessageReceipt
(
message
:
MessageLike
,
opts
?:
{
confirmations
?:
number
pollIntervalMs
?:
number
timeoutMs
?:
number
}
):
Promise
<
MessageReceipt
>
/**
* Estimates the amount of gas required to fully execute a given message on L2. Only applies to
* L1 => L2 messages. You would supply this gas limit when sending the message to L2.
*
* @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
:
MessageRequestLike
,
opts
?:
{
bufferPercent
?:
number
from
?:
string
}
):
Promise
<
BigNumber
>
/**
* Returns the estimated amount of time before the message can be executed. When this is a
* message being sent to L1, this will return the estimated time until the message will complete
* its challenge period. When this is a message being sent to L2, this will return the estimated
* amount of time until the message will be picked up and executed on L2.
*
* @param message Message to estimate the time remaining for.
* @returns Estimated amount of time remaining (in seconds) before the message can be executed.
*/
estimateMessageWaitTimeSeconds
(
message
:
MessageLike
):
Promise
<
number
>
/**
* Queries the current challenge period in seconds from the StateCommitmentChain.
*
* @returns Current challenge period in seconds.
*/
getChallengePeriodSeconds
():
Promise
<
number
>
/**
* Returns the state root that corresponds to a given message. This is the state root for the
* block in which the transaction was included, as published to the StateCommitmentChain. If the
* state root for the given message has not been published yet, this function returns null.
*
* @param message Message to find a state root for.
* @returns State root for the block in which the message was created.
*/
getMessageStateRoot
(
message
:
MessageLike
):
Promise
<
StateRoot
|
null
>
/**
* Returns the StateBatchAppended event that was emitted when the batch with a given index was
* created. Returns null if no such event exists (the batch has not been submitted).
*
* @param batchIndex Index of the batch to find an event for.
* @returns StateBatchAppended event for the batch, or null if no such batch exists.
*/
getStateBatchAppendedEventByBatchIndex
(
batchIndex
:
number
):
Promise
<
Event
|
null
>
/**
* Returns the StateBatchAppended event for the batch that includes the transaction with the
* given index. Returns null if no such event exists.
*
* @param transactionIndex Index of the L2 transaction to find an event for.
* @returns StateBatchAppended event for the batch that includes the given transaction by index.
*/
getStateBatchAppendedEventByTransactionIndex
(
transactionIndex
:
number
):
Promise
<
Event
|
null
>
/**
* Returns information about the state root batch that included the state root for the given
* transaction by index. Returns null if no such state root has been published yet.
*
* @param transactionIndex Index of the L2 transaction to find a state root batch for.
* @returns State root batch for the given transaction index, or null if none exists yet.
*/
getStateRootBatchByTransactionIndex
(
transactionIndex
:
number
):
Promise
<
StateRootBatch
|
null
>
/**
* Generates the proof required to finalize an L2 to L1 message.
*
* @param message Message to generate a proof for.
* @returns Proof that can be used to finalize the message.
*/
getMessageProof
(
message
:
MessageLike
):
Promise
<
CrossChainMessageProof
>
}
packages/sdk/src/interfaces/index.ts
View file @
77bd8dfc
export
*
from
'
./bridge-adapter
'
export
*
from
'
./bridge-adapter
'
export
*
from
'
./cross-chain-messenger
'
export
*
from
'
./cross-chain-messenger
'
export
*
from
'
./cross-chain-provider
'
export
*
from
'
./l2-provider
'
export
*
from
'
./l2-provider
'
export
*
from
'
./types
'
export
*
from
'
./types
'
packages/sdk/src/interfaces/types.ts
View file @
77bd8dfc
...
@@ -6,7 +6,7 @@ import {
...
@@ -6,7 +6,7 @@ import {
import
{
Signer
}
from
'
@ethersproject/abstract-signer
'
import
{
Signer
}
from
'
@ethersproject/abstract-signer
'
import
{
Contract
,
BigNumber
}
from
'
ethers
'
import
{
Contract
,
BigNumber
}
from
'
ethers
'
import
{
ICrossChain
Provider
}
from
'
./cross-chain-provid
er
'
import
{
ICrossChain
Messenger
}
from
'
./cross-chain-messeng
er
'
import
{
IBridgeAdapter
}
from
'
./bridge-adapter
'
import
{
IBridgeAdapter
}
from
'
./bridge-adapter
'
/**
/**
...
@@ -76,7 +76,7 @@ export interface OEContractsLike {
...
@@ -76,7 +76,7 @@ export interface OEContractsLike {
export
interface
BridgeAdapterData
{
export
interface
BridgeAdapterData
{
[
name
:
string
]:
{
[
name
:
string
]:
{
Adapter
:
new
(
opts
:
{
Adapter
:
new
(
opts
:
{
provider
:
ICrossChainProvid
er
messenger
:
ICrossChainMesseng
er
l1Bridge
:
AddressLike
l1Bridge
:
AddressLike
l2Bridge
:
AddressLike
l2Bridge
:
AddressLike
})
=>
IBridgeAdapter
})
=>
IBridgeAdapter
...
...
packages/sdk/src/utils/coercion.ts
View file @
77bd8dfc
...
@@ -5,27 +5,32 @@ import {
...
@@ -5,27 +5,32 @@ import {
TransactionReceipt
,
TransactionReceipt
,
TransactionResponse
,
TransactionResponse
,
}
from
'
@ethersproject/abstract-provider
'
}
from
'
@ethersproject/abstract-provider
'
import
{
Signer
}
from
'
@ethersproject/abstract-signer
'
import
{
ethers
,
BigNumber
}
from
'
ethers
'
import
{
ethers
,
BigNumber
}
from
'
ethers
'
import
{
import
{
ProviderLike
,
SignerOr
ProviderLike
,
TransactionLike
,
TransactionLike
,
NumberLike
,
NumberLike
,
AddressLike
,
AddressLike
,
}
from
'
../interfaces
'
}
from
'
../interfaces
'
/**
/**
* Converts a
ProviderLike into a provider. Assumes that if the ProviderLike is a string then
* Converts a
SignerOrProviderLike into a Signer or a Provider. Assumes that if the input is a
* it is a JSON-RPC url.
*
string then
it is a JSON-RPC url.
*
*
* @param
provider ProviderLike to turn into a p
rovider.
* @param
signerOrProvider SignerOrProviderLike to turn into a Signer or P
rovider.
* @returns
ProviderLike as a p
rovider.
* @returns
Input as a Signer or P
rovider.
*/
*/
export
const
toProvider
=
(
provider
:
ProviderLike
):
Provider
=>
{
export
const
toSignerOrProvider
=
(
if
(
typeof
provider
===
'
string
'
)
{
signerOrProvider
:
SignerOrProviderLike
return
new
ethers
.
providers
.
JsonRpcProvider
(
provider
)
):
Signer
|
Provider
=>
{
}
else
if
(
Provider
.
isProvider
(
provider
))
{
if
(
typeof
signerOrProvider
===
'
string
'
)
{
return
provider
return
new
ethers
.
providers
.
JsonRpcProvider
(
signerOrProvider
)
}
else
if
(
Provider
.
isProvider
(
signerOrProvider
))
{
return
signerOrProvider
as
Provider
}
else
if
(
Signer
.
isSigner
(
signerOrProvider
))
{
return
signerOrProvider
as
Signer
}
else
{
}
else
{
throw
new
Error
(
'
Invalid provider
'
)
throw
new
Error
(
'
Invalid provider
'
)
}
}
...
...
packages/sdk/src/utils/contracts.ts
View file @
77bd8dfc
...
@@ -12,7 +12,7 @@ import {
...
@@ -12,7 +12,7 @@ import {
AddressLike
,
AddressLike
,
BridgeAdapters
,
BridgeAdapters
,
BridgeAdapterData
,
BridgeAdapterData
,
ICrossChain
Provid
er
,
ICrossChain
Messeng
er
,
}
from
'
../interfaces
'
}
from
'
../interfaces
'
import
{
import
{
StandardBridgeAdapter
,
StandardBridgeAdapter
,
...
@@ -291,14 +291,14 @@ export const getAllOEContracts = (
...
@@ -291,14 +291,14 @@ export const getAllOEContracts = (
* Gets a series of bridge adapters for the given L1 chain ID.
* Gets a series of bridge adapters for the given L1 chain ID.
*
*
* @param l1ChainId L1 chain ID for the L1 network where the custom bridges are deployed.
* @param l1ChainId L1 chain ID for the L1 network where the custom bridges are deployed.
* @param
provider Cross chain provid
er to connect to the bridge adapters
* @param
messenger Cross chain messeng
er to connect to the bridge adapters
* @param opts Additional options for connecting to the custom bridges.
* @param opts Additional options for connecting to the custom bridges.
* @param opts.overrides Custom bridge adapters.
* @param opts.overrides Custom bridge adapters.
* @returns An object containing all bridge adapters
* @returns An object containing all bridge adapters
*/
*/
export
const
getBridgeAdapters
=
(
export
const
getBridgeAdapters
=
(
l1ChainId
:
number
,
l1ChainId
:
number
,
provider
:
ICrossChainProvid
er
,
messenger
:
ICrossChainMesseng
er
,
opts
?:
{
opts
?:
{
overrides
?:
BridgeAdapterData
overrides
?:
BridgeAdapterData
}
}
...
@@ -309,7 +309,7 @@ export const getBridgeAdapters = (
...
@@ -309,7 +309,7 @@ export const getBridgeAdapters = (
...(
opts
?.
overrides
||
{}),
...(
opts
?.
overrides
||
{}),
}))
{
}))
{
adapters
[
bridgeName
]
=
new
bridgeData
.
Adapter
({
adapters
[
bridgeName
]
=
new
bridgeData
.
Adapter
({
provid
er
,
messeng
er
,
l1Bridge
:
bridgeData
.
l1Bridge
,
l1Bridge
:
bridgeData
.
l1Bridge
,
l2Bridge
:
bridgeData
.
l2Bridge
,
l2Bridge
:
bridgeData
.
l2Bridge
,
})
})
...
...
packages/sdk/test/cross-chain-messenger.spec.ts
View file @
77bd8dfc
import
{
Provider
}
from
'
@ethersproject/abstract-provider
'
import
{
expectApprox
}
from
'
@eth-optimism/core-utils
'
import
{
predeploys
}
from
'
@eth-optimism/contracts
'
import
{
Contract
}
from
'
ethers
'
import
{
Contract
}
from
'
ethers
'
import
{
ethers
}
from
'
hardhat
'
import
{
ethers
}
from
'
hardhat
'
import
{
predeploys
}
from
'
@eth-optimism/contracts
'
import
{
expect
}
from
'
./setup
'
import
{
expect
}
from
'
./setup
'
import
{
import
{
CrossChainProvider
,
MessageDirection
,
CrossChainMessenger
,
CONTRACT_ADDRESSES
,
MessageDirection
,
hashCrossChainMessage
,
ETHBridgeAdapter
,
omit
,
}
from
'
../src
'
MessageStatus
,
CrossChainMessage
,
CrossChainMessenger
,
StandardBridgeAdapter
,
ETHBridgeAdapter
,
}
from
'
../src
'
import
{
DUMMY_MESSAGE
}
from
'
./helpers
'
describe
(
'
CrossChainMessenger
'
,
()
=>
{
let
l1Signer
:
any
let
l2Signer
:
any
before
(
async
()
=>
{
;[
l1Signer
,
l2Signer
]
=
await
ethers
.
getSigners
()
})
describe
(
'
construction
'
,
()
=>
{
describe
(
'
when given an ethers provider for the L1 provider
'
,
()
=>
{
it
(
'
should use the provider as the L1 provider
'
,
()
=>
{
const
messenger
=
new
CrossChainMessenger
({
l1SignerOrProvider
:
ethers
.
provider
,
l2SignerOrProvider
:
ethers
.
provider
,
l1ChainId
:
1
,
})
expect
(
messenger
.
l1Provider
).
to
.
equal
(
ethers
.
provider
)
})
})
describe
(
'
when given an ethers provider for the L2 provider
'
,
()
=>
{
it
(
'
should use the provider as the L2 provider
'
,
()
=>
{
const
messenger
=
new
CrossChainMessenger
({
l1SignerOrProvider
:
ethers
.
provider
,
l2SignerOrProvider
:
ethers
.
provider
,
l1ChainId
:
1
,
})
expect
(
messenger
.
l2Provider
).
to
.
equal
(
ethers
.
provider
)
})
})
describe
(
'
when given a string as the L1 provider
'
,
()
=>
{
it
(
'
should create a JSON-RPC provider for the L1 provider
'
,
()
=>
{
const
messenger
=
new
CrossChainMessenger
({
l1SignerOrProvider
:
'
https://localhost:8545
'
,
l2SignerOrProvider
:
ethers
.
provider
,
l1ChainId
:
1
,
})
expect
(
Provider
.
isProvider
(
messenger
.
l1Provider
)).
to
.
be
.
true
})
})
describe
(
'
when given a string as the L2 provider
'
,
()
=>
{
it
(
'
should create a JSON-RPC provider for the L2 provider
'
,
()
=>
{
const
messenger
=
new
CrossChainMessenger
({
l1SignerOrProvider
:
ethers
.
provider
,
l2SignerOrProvider
:
'
https://localhost:8545
'
,
l1ChainId
:
1
,
})
expect
(
Provider
.
isProvider
(
messenger
.
l2Provider
)).
to
.
be
.
true
})
})
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
'
,
()
=>
{
const
messenger
=
new
CrossChainMessenger
({
l1SignerOrProvider
:
ethers
.
provider
,
l2SignerOrProvider
:
'
https://localhost:8545
'
,
l1ChainId
:
1
,
})
const
addresses
=
CONTRACT_ADDRESSES
[
1
]
for
(
const
[
contractName
,
contractAddress
]
of
Object
.
entries
(
addresses
.
l1
))
{
const
contract
=
messenger
.
contracts
.
l1
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
contractAddress
)
}
for
(
const
[
contractName
,
contractAddress
]
of
Object
.
entries
(
addresses
.
l2
))
{
const
contract
=
messenger
.
contracts
.
l2
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
contractAddress
)
}
})
})
describe
(
'
when given an unknown chain ID
'
,
()
=>
{
it
(
'
should throw an error
'
,
()
=>
{
expect
(()
=>
{
new
CrossChainMessenger
({
l1SignerOrProvider
:
ethers
.
provider
,
l2SignerOrProvider
:
'
https://localhost:8545
'
,
l1ChainId
:
1234
,
})
}).
to
.
throw
()
})
})
})
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
'
,
()
=>
{
const
overrides
=
{
l1
:
{
L1CrossDomainMessenger
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
},
l2
:
{
L2CrossDomainMessenger
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
},
}
const
messenger
=
new
CrossChainMessenger
({
l1SignerOrProvider
:
ethers
.
provider
,
l2SignerOrProvider
:
'
https://localhost:8545
'
,
l1ChainId
:
1
,
contracts
:
overrides
,
})
const
addresses
=
CONTRACT_ADDRESSES
[
1
]
for
(
const
[
contractName
,
contractAddress
]
of
Object
.
entries
(
addresses
.
l1
))
{
if
(
overrides
.
l1
[
contractName
])
{
const
contract
=
messenger
.
contracts
.
l1
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
overrides
.
l1
[
contractName
])
}
else
{
const
contract
=
messenger
.
contracts
.
l1
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
contractAddress
)
}
}
for
(
const
[
contractName
,
contractAddress
]
of
Object
.
entries
(
addresses
.
l2
))
{
if
(
overrides
.
l2
[
contractName
])
{
const
contract
=
messenger
.
contracts
.
l2
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
overrides
.
l2
[
contractName
])
}
else
{
const
contract
=
messenger
.
contracts
.
l2
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
contractAddress
)
}
}
})
})
describe
(
'
when given an unknown chain ID
'
,
()
=>
{
describe
(
'
when all L1 addresses are provided
'
,
()
=>
{
it
(
'
should use custom addresses where provided
'
,
()
=>
{
const
overrides
=
{
l1
:
{
AddressManager
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
L1CrossDomainMessenger
:
'
0x
'
+
'
12
'
.
repeat
(
20
),
L1StandardBridge
:
'
0x
'
+
'
13
'
.
repeat
(
20
),
StateCommitmentChain
:
'
0x
'
+
'
14
'
.
repeat
(
20
),
CanonicalTransactionChain
:
'
0x
'
+
'
15
'
.
repeat
(
20
),
BondManager
:
'
0x
'
+
'
16
'
.
repeat
(
20
),
},
l2
:
{
L2CrossDomainMessenger
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
},
}
const
messenger
=
new
CrossChainMessenger
({
l1SignerOrProvider
:
ethers
.
provider
,
l2SignerOrProvider
:
'
https://localhost:8545
'
,
l1ChainId
:
1234
,
contracts
:
overrides
,
})
const
addresses
=
CONTRACT_ADDRESSES
[
1
]
for
(
const
[
contractName
,
contractAddress
]
of
Object
.
entries
(
addresses
.
l1
))
{
if
(
overrides
.
l1
[
contractName
])
{
const
contract
=
messenger
.
contracts
.
l1
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
overrides
.
l1
[
contractName
])
}
else
{
const
contract
=
messenger
.
contracts
.
l1
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
contractAddress
)
}
}
for
(
const
[
contractName
,
contractAddress
]
of
Object
.
entries
(
addresses
.
l2
))
{
if
(
overrides
.
l2
[
contractName
])
{
const
contract
=
messenger
.
contracts
.
l2
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
overrides
.
l2
[
contractName
])
}
else
{
const
contract
=
messenger
.
contracts
.
l2
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
contractAddress
)
}
}
})
})
describe
(
'
when not all L1 addresses are provided
'
,
()
=>
{
it
(
'
should throw an error
'
,
()
=>
{
expect
(()
=>
{
new
CrossChainMessenger
({
l1SignerOrProvider
:
ethers
.
provider
,
l2SignerOrProvider
:
'
https://localhost:8545
'
,
l1ChainId
:
1234
,
contracts
:
{
l1
:
{
// Missing some required L1 addresses
AddressManager
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
L1CrossDomainMessenger
:
'
0x
'
+
'
12
'
.
repeat
(
20
),
L1StandardBridge
:
'
0x
'
+
'
13
'
.
repeat
(
20
),
},
l2
:
{
L2CrossDomainMessenger
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
},
},
})
}).
to
.
throw
()
})
})
})
})
})
describe
(
'
getMessagesByTransaction
'
,
()
=>
{
let
l1Messenger
:
Contract
let
l2Messenger
:
Contract
let
messenger
:
CrossChainMessenger
beforeEach
(
async
()
=>
{
l1Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
l2Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
messenger
=
new
CrossChainMessenger
({
l1SignerOrProvider
:
ethers
.
provider
,
l2SignerOrProvider
:
ethers
.
provider
,
l1ChainId
:
31337
,
contracts
:
{
l1
:
{
L1CrossDomainMessenger
:
l1Messenger
.
address
,
},
l2
:
{
L2CrossDomainMessenger
:
l2Messenger
.
address
,
},
},
})
})
describe
(
'
when a direction is specified
'
,
()
=>
{
describe
(
'
when the transaction exists
'
,
()
=>
{
describe
(
'
when the transaction has messages
'
,
()
=>
{
for
(
const
n
of
[
1
,
2
,
4
,
8
])
{
it
(
`should find
${
n
}
messages when the transaction emits
${
n
}
messages`
,
async
()
=>
{
const
messages
=
[...
Array
(
n
)].
map
(()
=>
{
return
DUMMY_MESSAGE
})
const
tx
=
await
l1Messenger
.
triggerSentMessageEvents
(
messages
)
const
found
=
await
messenger
.
getMessagesByTransaction
(
tx
,
{
direction
:
MessageDirection
.
L1_TO_L2
,
})
expect
(
found
).
to
.
deep
.
equal
(
messages
.
map
((
message
,
i
)
=>
{
return
{
direction
:
MessageDirection
.
L1_TO_L2
,
sender
:
message
.
sender
,
target
:
message
.
target
,
message
:
message
.
message
,
messageNonce
:
ethers
.
BigNumber
.
from
(
message
.
messageNonce
),
gasLimit
:
ethers
.
BigNumber
.
from
(
message
.
gasLimit
),
logIndex
:
i
,
blockNumber
:
tx
.
blockNumber
,
transactionHash
:
tx
.
hash
,
}
})
)
})
}
})
describe
(
'
when the transaction has no messages
'
,
()
=>
{
it
(
'
should find nothing
'
,
async
()
=>
{
const
tx
=
await
l1Messenger
.
doNothing
()
const
found
=
await
messenger
.
getMessagesByTransaction
(
tx
,
{
direction
:
MessageDirection
.
L1_TO_L2
,
})
expect
(
found
).
to
.
deep
.
equal
([])
})
})
})
describe
(
'
when the transaction does not exist in the specified direction
'
,
()
=>
{
it
(
'
should throw an error
'
,
async
()
=>
{
await
expect
(
messenger
.
getMessagesByTransaction
(
'
0x
'
+
'
11
'
.
repeat
(
32
),
{
direction
:
MessageDirection
.
L1_TO_L2
,
})
).
to
.
be
.
rejectedWith
(
'
unable to find transaction receipt
'
)
})
})
})
describe
(
'
when a direction is not specified
'
,
()
=>
{
describe
(
'
when the transaction exists only on L1
'
,
()
=>
{
describe
(
'
when the transaction has messages
'
,
()
=>
{
for
(
const
n
of
[
1
,
2
,
4
,
8
])
{
it
(
`should find
${
n
}
messages when the transaction emits
${
n
}
messages`
,
async
()
=>
{
const
messages
=
[...
Array
(
n
)].
map
(()
=>
{
return
DUMMY_MESSAGE
})
const
tx
=
await
l1Messenger
.
triggerSentMessageEvents
(
messages
)
const
found
=
await
messenger
.
getMessagesByTransaction
(
tx
)
expect
(
found
).
to
.
deep
.
equal
(
messages
.
map
((
message
,
i
)
=>
{
return
{
direction
:
MessageDirection
.
L1_TO_L2
,
sender
:
message
.
sender
,
target
:
message
.
target
,
message
:
message
.
message
,
messageNonce
:
ethers
.
BigNumber
.
from
(
message
.
messageNonce
),
gasLimit
:
ethers
.
BigNumber
.
from
(
message
.
gasLimit
),
logIndex
:
i
,
blockNumber
:
tx
.
blockNumber
,
transactionHash
:
tx
.
hash
,
}
})
)
})
}
})
describe
(
'
when the transaction has no messages
'
,
()
=>
{
it
(
'
should find nothing
'
,
async
()
=>
{
const
tx
=
await
l1Messenger
.
doNothing
()
const
found
=
await
messenger
.
getMessagesByTransaction
(
tx
)
expect
(
found
).
to
.
deep
.
equal
([])
})
})
})
describe
(
'
when the transaction exists only on L2
'
,
()
=>
{
describe
(
'
when the transaction has messages
'
,
()
=>
{
for
(
const
n
of
[
1
,
2
,
4
,
8
])
{
it
(
`should find
${
n
}
messages when the transaction emits
${
n
}
messages`
,
()
=>
{
// TODO: Need support for simulating more than one network.
})
}
})
describe
(
'
when the transaction has no messages
'
,
()
=>
{
it
(
'
should find nothing
'
,
()
=>
{
// TODO: Need support for simulating more than one network.
})
})
})
describe
(
'
when the transaction does not exist
'
,
()
=>
{
it
(
'
should throw an error
'
,
async
()
=>
{
await
expect
(
messenger
.
getMessagesByTransaction
(
'
0x
'
+
'
11
'
.
repeat
(
32
))
).
to
.
be
.
rejectedWith
(
'
unable to find transaction receipt
'
)
})
})
describe
(
'
when the transaction exists on both L1 and L2
'
,
()
=>
{
it
(
'
should throw an error
'
,
async
()
=>
{
// TODO: Need support for simulating more than one network.
})
})
})
})
describe
(
'
getMessagesByAddress
'
,
()
=>
{
describe
(
'
when the address has sent messages
'
,
()
=>
{
describe
(
'
when no direction is specified
'
,
()
=>
{
it
(
'
should find all messages sent by the address
'
)
})
describe
(
'
when a direction is specified
'
,
()
=>
{
it
(
'
should find all messages only in the given direction
'
)
})
describe
(
'
when a block range is specified
'
,
()
=>
{
it
(
'
should find all messages within the block range
'
)
})
describe
(
'
when both a direction and a block range are specified
'
,
()
=>
{
it
(
'
should find all messages only in the given direction and within the block range
'
)
})
})
describe
(
'
when the address has not sent messages
'
,
()
=>
{
it
(
'
should find nothing
'
)
})
})
describe
(
'
getTokenBridgeMessagesByAddress
'
,
()
=>
{
let
l1Bridge
:
Contract
let
l2Bridge
:
Contract
let
l1Messenger
:
Contract
let
l2Messenger
:
Contract
let
messenger
:
CrossChainMessenger
beforeEach
(
async
()
=>
{
l1Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
l2Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
l1Bridge
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockBridge
'
)
).
deploy
(
l1Messenger
.
address
))
as
any
l2Bridge
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockBridge
'
)
).
deploy
(
l2Messenger
.
address
))
as
any
messenger
=
new
CrossChainMessenger
({
l1SignerOrProvider
:
ethers
.
provider
,
l2SignerOrProvider
:
ethers
.
provider
,
l1ChainId
:
31337
,
contracts
:
{
l1
:
{
L1CrossDomainMessenger
:
l1Messenger
.
address
,
L1StandardBridge
:
l1Bridge
.
address
,
},
l2
:
{
L2CrossDomainMessenger
:
l2Messenger
.
address
,
L2StandardBridge
:
l2Bridge
.
address
,
},
},
bridges
:
{
Standard
:
{
Adapter
:
StandardBridgeAdapter
,
l1Bridge
:
l1Bridge
.
address
,
l2Bridge
:
l2Bridge
.
address
,
},
},
})
})
describe
(
'
when the address has made deposits or withdrawals
'
,
()
=>
{
describe
(
'
when a direction of L1 => L2 is specified
'
,
()
=>
{
it
(
'
should find all deposits made by the address
'
,
async
()
=>
{
const
from
=
'
0x
'
+
'
99
'
.
repeat
(
20
)
const
deposit
=
{
l1Token
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
l2Token
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
from
,
to
:
'
0x
'
+
'
44
'
.
repeat
(
20
),
amount
:
ethers
.
BigNumber
.
from
(
1234
),
data
:
'
0x1234
'
,
}
const
withdrawal
=
{
l1Token
:
'
0x
'
+
'
12
'
.
repeat
(
20
),
l2Token
:
'
0x
'
+
'
23
'
.
repeat
(
20
),
from
,
to
:
'
0x
'
+
'
45
'
.
repeat
(
20
),
amount
:
ethers
.
BigNumber
.
from
(
5678
),
data
:
'
0x5678
'
,
}
await
l1Bridge
.
emitERC20DepositInitiated
(
deposit
)
await
l2Bridge
.
emitWithdrawalInitiated
(
withdrawal
)
const
found
=
await
messenger
.
getTokenBridgeMessagesByAddress
(
from
,
{
direction
:
MessageDirection
.
L1_TO_L2
,
})
expect
(
found
.
length
).
to
.
equal
(
1
)
expect
(
found
[
0
].
amount
).
to
.
deep
.
equal
(
deposit
.
amount
)
expect
(
found
[
0
].
data
).
to
.
deep
.
equal
(
deposit
.
data
)
expect
(
found
[
0
].
direction
).
to
.
equal
(
MessageDirection
.
L1_TO_L2
)
expect
(
found
[
0
].
l1Token
).
to
.
deep
.
equal
(
deposit
.
l1Token
)
expect
(
found
[
0
].
l2Token
).
to
.
deep
.
equal
(
deposit
.
l2Token
)
expect
(
found
[
0
].
from
).
to
.
deep
.
equal
(
deposit
.
from
)
expect
(
found
[
0
].
to
).
to
.
deep
.
equal
(
deposit
.
to
)
})
})
describe
(
'
when a direction of L2 => L1 is specified
'
,
()
=>
{
it
(
'
should find all withdrawals made by the address
'
,
async
()
=>
{
const
from
=
'
0x
'
+
'
99
'
.
repeat
(
20
)
const
deposit
=
{
l1Token
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
l2Token
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
from
,
to
:
'
0x
'
+
'
44
'
.
repeat
(
20
),
amount
:
ethers
.
BigNumber
.
from
(
1234
),
data
:
'
0x1234
'
,
}
const
withdrawal
=
{
l1Token
:
'
0x
'
+
'
12
'
.
repeat
(
20
),
l2Token
:
'
0x
'
+
'
23
'
.
repeat
(
20
),
from
,
to
:
'
0x
'
+
'
45
'
.
repeat
(
20
),
amount
:
ethers
.
BigNumber
.
from
(
5678
),
data
:
'
0x5678
'
,
}
await
l1Bridge
.
emitERC20DepositInitiated
(
deposit
)
await
l2Bridge
.
emitWithdrawalInitiated
(
withdrawal
)
const
found
=
await
messenger
.
getTokenBridgeMessagesByAddress
(
from
,
{
direction
:
MessageDirection
.
L2_TO_L1
,
})
expect
(
found
.
length
).
to
.
equal
(
1
)
expect
(
found
[
0
].
amount
).
to
.
deep
.
equal
(
withdrawal
.
amount
)
expect
(
found
[
0
].
data
).
to
.
deep
.
equal
(
withdrawal
.
data
)
expect
(
found
[
0
].
direction
).
to
.
equal
(
MessageDirection
.
L2_TO_L1
)
expect
(
found
[
0
].
l1Token
).
to
.
deep
.
equal
(
withdrawal
.
l1Token
)
expect
(
found
[
0
].
l2Token
).
to
.
deep
.
equal
(
withdrawal
.
l2Token
)
expect
(
found
[
0
].
from
).
to
.
deep
.
equal
(
withdrawal
.
from
)
expect
(
found
[
0
].
to
).
to
.
deep
.
equal
(
withdrawal
.
to
)
})
})
describe
(
'
when no direction is specified
'
,
()
=>
{
it
(
'
should find all deposits and withdrawals made by the address
'
,
async
()
=>
{
const
from
=
'
0x
'
+
'
99
'
.
repeat
(
20
)
const
deposit
=
{
l1Token
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
l2Token
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
from
,
to
:
'
0x
'
+
'
44
'
.
repeat
(
20
),
amount
:
ethers
.
BigNumber
.
from
(
1234
),
data
:
'
0x1234
'
,
}
const
withdrawal
=
{
l1Token
:
'
0x
'
+
'
12
'
.
repeat
(
20
),
l2Token
:
'
0x
'
+
'
23
'
.
repeat
(
20
),
from
,
to
:
'
0x
'
+
'
45
'
.
repeat
(
20
),
amount
:
ethers
.
BigNumber
.
from
(
5678
),
data
:
'
0x5678
'
,
}
await
l1Bridge
.
emitERC20DepositInitiated
(
deposit
)
await
l2Bridge
.
emitWithdrawalInitiated
(
withdrawal
)
const
found
=
await
messenger
.
getTokenBridgeMessagesByAddress
(
from
)
expect
(
found
.
length
).
to
.
equal
(
2
)
// Check the deposit (deposits get searched first)
expect
(
found
[
0
].
amount
).
to
.
deep
.
equal
(
deposit
.
amount
)
expect
(
found
[
0
].
data
).
to
.
deep
.
equal
(
deposit
.
data
)
expect
(
found
[
0
].
direction
).
to
.
equal
(
MessageDirection
.
L1_TO_L2
)
expect
(
found
[
0
].
l1Token
).
to
.
deep
.
equal
(
deposit
.
l1Token
)
expect
(
found
[
0
].
l2Token
).
to
.
deep
.
equal
(
deposit
.
l2Token
)
expect
(
found
[
0
].
from
).
to
.
deep
.
equal
(
deposit
.
from
)
expect
(
found
[
0
].
to
).
to
.
deep
.
equal
(
deposit
.
to
)
// Check the withdrawal
expect
(
found
[
1
].
amount
).
to
.
deep
.
equal
(
withdrawal
.
amount
)
expect
(
found
[
1
].
data
).
to
.
deep
.
equal
(
withdrawal
.
data
)
expect
(
found
[
1
].
direction
).
to
.
equal
(
MessageDirection
.
L2_TO_L1
)
expect
(
found
[
1
].
l1Token
).
to
.
deep
.
equal
(
withdrawal
.
l1Token
)
expect
(
found
[
1
].
l2Token
).
to
.
deep
.
equal
(
withdrawal
.
l2Token
)
expect
(
found
[
1
].
from
).
to
.
deep
.
equal
(
withdrawal
.
from
)
expect
(
found
[
1
].
to
).
to
.
deep
.
equal
(
withdrawal
.
to
)
})
})
})
describe
(
'
when the address has not made any deposits or withdrawals
'
,
()
=>
{
it
(
'
should find nothing
'
,
async
()
=>
{
const
from
=
'
0x
'
+
'
99
'
.
repeat
(
20
)
const
found
=
await
messenger
.
getTokenBridgeMessagesByAddress
(
from
)
expect
(
found
).
to
.
deep
.
equal
([])
})
})
})
describe
(
'
toCrossChainMessage
'
,
()
=>
{
let
l1Bridge
:
Contract
let
l2Bridge
:
Contract
let
l1Messenger
:
Contract
let
l2Messenger
:
Contract
let
messenger
:
CrossChainMessenger
beforeEach
(
async
()
=>
{
l1Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
l2Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
l1Bridge
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockBridge
'
)
).
deploy
(
l1Messenger
.
address
))
as
any
l2Bridge
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockBridge
'
)
).
deploy
(
l2Messenger
.
address
))
as
any
messenger
=
new
CrossChainMessenger
({
l1SignerOrProvider
:
ethers
.
provider
,
l2SignerOrProvider
:
ethers
.
provider
,
l1ChainId
:
31337
,
contracts
:
{
l1
:
{
L1CrossDomainMessenger
:
l1Messenger
.
address
,
L1StandardBridge
:
l1Bridge
.
address
,
},
l2
:
{
L2CrossDomainMessenger
:
l2Messenger
.
address
,
L2StandardBridge
:
l2Bridge
.
address
,
},
},
bridges
:
{
Standard
:
{
Adapter
:
StandardBridgeAdapter
,
l1Bridge
:
l1Bridge
.
address
,
l2Bridge
:
l2Bridge
.
address
,
},
},
})
})
describe
(
'
when the input is a CrossChainMessage
'
,
()
=>
{
it
(
'
should return the input
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
gasLimit
:
0
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
expect
(
await
messenger
.
toCrossChainMessage
(
message
)).
to
.
deep
.
equal
(
message
)
})
})
describe
(
'
when the input is a TokenBridgeMessage
'
,
()
=>
{
// TODO: There are some edge cases here with custom bridges that conform to the interface but
// not to the behavioral spec. Possibly worth testing those. For now this is probably
// sufficient.
it
(
'
should return the sent message event that came after the deposit or withdrawal
'
,
async
()
=>
{
const
from
=
'
0x
'
+
'
99
'
.
repeat
(
20
)
const
deposit
=
{
l1Token
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
l2Token
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
from
,
to
:
'
0x
'
+
'
44
'
.
repeat
(
20
),
amount
:
ethers
.
BigNumber
.
from
(
1234
),
data
:
'
0x1234
'
,
}
const
tx
=
await
l1Bridge
.
emitERC20DepositInitiated
(
deposit
)
const
foundCrossChainMessages
=
await
messenger
.
getMessagesByTransaction
(
tx
)
const
foundTokenBridgeMessages
=
await
messenger
.
getTokenBridgeMessagesByAddress
(
from
)
const
resolved
=
await
messenger
.
toCrossChainMessage
(
foundTokenBridgeMessages
[
0
]
)
expect
(
resolved
).
to
.
deep
.
equal
(
foundCrossChainMessages
[
0
])
})
})
describe
(
'
when the input is a TransactionLike
'
,
()
=>
{
describe
(
'
when the transaction sent exactly one message
'
,
()
=>
{
it
(
'
should return the CrossChainMessage sent in the transaction
'
,
async
()
=>
{
const
tx
=
await
l1Messenger
.
triggerSentMessageEvents
([
DUMMY_MESSAGE
])
const
foundCrossChainMessages
=
await
messenger
.
getMessagesByTransaction
(
tx
)
const
resolved
=
await
messenger
.
toCrossChainMessage
(
tx
)
expect
(
resolved
).
to
.
deep
.
equal
(
foundCrossChainMessages
[
0
])
})
})
describe
(
'
when the transaction sent more than one message
'
,
()
=>
{
it
(
'
should throw an error
'
,
async
()
=>
{
const
messages
=
[...
Array
(
2
)].
map
(()
=>
{
return
DUMMY_MESSAGE
})
const
tx
=
await
l1Messenger
.
triggerSentMessageEvents
(
messages
)
await
expect
(
messenger
.
toCrossChainMessage
(
tx
)).
to
.
be
.
rejectedWith
(
'
expected 1 message, got 2
'
)
})
})
describe
(
'
when the transaction sent no messages
'
,
()
=>
{
it
(
'
should throw an error
'
,
async
()
=>
{
const
tx
=
await
l1Messenger
.
triggerSentMessageEvents
([])
await
expect
(
messenger
.
toCrossChainMessage
(
tx
)).
to
.
be
.
rejectedWith
(
'
expected 1 message, got 0
'
)
})
})
})
})
describe
(
'
getMessageStatus
'
,
()
=>
{
let
scc
:
Contract
let
l1Messenger
:
Contract
let
l2Messenger
:
Contract
let
messenger
:
CrossChainMessenger
beforeEach
(
async
()
=>
{
// TODO: Get rid of the nested awaits here. Could be a good first issue for someone.
scc
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockSCC
'
)).
deploy
())
as
any
l1Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
l2Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
messenger
=
new
CrossChainMessenger
({
l1SignerOrProvider
:
ethers
.
provider
,
l2SignerOrProvider
:
ethers
.
provider
,
l1ChainId
:
31337
,
contracts
:
{
l1
:
{
L1CrossDomainMessenger
:
l1Messenger
.
address
,
StateCommitmentChain
:
scc
.
address
,
},
l2
:
{
L2CrossDomainMessenger
:
l2Messenger
.
address
,
},
},
})
})
const
sendAndGetDummyMessage
=
async
(
direction
:
MessageDirection
)
=>
{
const
mockMessenger
=
direction
===
MessageDirection
.
L1_TO_L2
?
l1Messenger
:
l2Messenger
const
tx
=
await
mockMessenger
.
triggerSentMessageEvents
([
DUMMY_MESSAGE
])
return
(
await
messenger
.
getMessagesByTransaction
(
tx
,
{
direction
,
})
)[
0
]
}
const
submitStateRootBatchForMessage
=
async
(
message
:
CrossChainMessage
)
=>
{
await
scc
.
setSBAParams
({
batchIndex
:
0
,
batchRoot
:
ethers
.
constants
.
HashZero
,
batchSize
:
1
,
prevTotalElements
:
message
.
blockNumber
,
extraData
:
'
0x
'
,
})
await
scc
.
appendStateBatch
([
ethers
.
constants
.
HashZero
],
0
)
}
describe
(
'
when the message is an L1 => L2 message
'
,
()
=>
{
describe
(
'
when the message has not been executed on L2 yet
'
,
()
=>
{
it
(
'
should return a status of UNCONFIRMED_L1_TO_L2_MESSAGE
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L1_TO_L2
)
expect
(
await
messenger
.
getMessageStatus
(
message
)).
to
.
equal
(
MessageStatus
.
UNCONFIRMED_L1_TO_L2_MESSAGE
)
})
})
describe
(
'
when the message has been executed on L2
'
,
()
=>
{
it
(
'
should return a status of RELAYED
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L1_TO_L2
)
await
l2Messenger
.
triggerRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
expect
(
await
messenger
.
getMessageStatus
(
message
)).
to
.
equal
(
MessageStatus
.
RELAYED
)
})
})
describe
(
'
when the message has been executed but failed
'
,
()
=>
{
it
(
'
should return a status of FAILED_L1_TO_L2_MESSAGE
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L1_TO_L2
)
await
l2Messenger
.
triggerFailedRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
expect
(
await
messenger
.
getMessageStatus
(
message
)).
to
.
equal
(
MessageStatus
.
FAILED_L1_TO_L2_MESSAGE
)
})
})
})
describe
(
'
when the message is an L2 => L1 message
'
,
()
=>
{
describe
(
'
when the message state root has not been published
'
,
()
=>
{
it
(
'
should return a status of STATE_ROOT_NOT_PUBLISHED
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L2_TO_L1
)
expect
(
await
messenger
.
getMessageStatus
(
message
)).
to
.
equal
(
MessageStatus
.
STATE_ROOT_NOT_PUBLISHED
)
})
})
describe
(
'
CrossChainMessenger
'
,
()
=>
{
describe
(
'
when the message state root is still in the challenge period
'
,
()
=>
{
let
l1Signer
:
any
it
(
'
should return a status of IN_CHALLENGE_PERIOD
'
,
async
()
=>
{
let
l2Signer
:
any
const
message
=
await
sendAndGetDummyMessage
(
before
(
async
()
=>
{
MessageDirection
.
L2_TO_L1
;[
l1Signer
,
l2Signer
]
=
await
ethers
.
getSigners
()
)
await
submitStateRootBatchForMessage
(
message
)
expect
(
await
messenger
.
getMessageStatus
(
message
)).
to
.
equal
(
MessageStatus
.
IN_CHALLENGE_PERIOD
)
})
})
describe
(
'
when the message is no longer in the challenge period
'
,
()
=>
{
describe
(
'
when the message has been relayed successfully
'
,
()
=>
{
it
(
'
should return a status of RELAYED
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L2_TO_L1
)
await
submitStateRootBatchForMessage
(
message
)
const
challengePeriod
=
await
messenger
.
getChallengePeriodSeconds
()
ethers
.
provider
.
send
(
'
evm_increaseTime
'
,
[
challengePeriod
+
1
])
ethers
.
provider
.
send
(
'
evm_mine
'
,
[])
await
l1Messenger
.
triggerRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
expect
(
await
messenger
.
getMessageStatus
(
message
)).
to
.
equal
(
MessageStatus
.
RELAYED
)
})
})
describe
(
'
when the message has been relayed but the relay failed
'
,
()
=>
{
it
(
'
should return a status of READY_FOR_RELAY
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L2_TO_L1
)
await
submitStateRootBatchForMessage
(
message
)
const
challengePeriod
=
await
messenger
.
getChallengePeriodSeconds
()
ethers
.
provider
.
send
(
'
evm_increaseTime
'
,
[
challengePeriod
+
1
])
ethers
.
provider
.
send
(
'
evm_mine
'
,
[])
await
l1Messenger
.
triggerFailedRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
expect
(
await
messenger
.
getMessageStatus
(
message
)).
to
.
equal
(
MessageStatus
.
READY_FOR_RELAY
)
})
})
describe
(
'
when the message has not been relayed
'
,
()
=>
{
it
(
'
should return a status of READY_FOR_RELAY
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L2_TO_L1
)
await
submitStateRootBatchForMessage
(
message
)
const
challengePeriod
=
await
messenger
.
getChallengePeriodSeconds
()
ethers
.
provider
.
send
(
'
evm_increaseTime
'
,
[
challengePeriod
+
1
])
ethers
.
provider
.
send
(
'
evm_mine
'
,
[])
expect
(
await
messenger
.
getMessageStatus
(
message
)).
to
.
equal
(
MessageStatus
.
READY_FOR_RELAY
)
})
})
})
})
describe
(
'
when the message does not exist
'
,
()
=>
{
// TODO: Figure out if this is the correct behavior. Mark suggests perhaps returning null.
it
(
'
should throw an error
'
)
})
})
})
describe
(
'
sendMessage
'
,
()
=>
{
describe
(
'
getMessageReceipt
'
,
()
=>
{
let
l1Bridge
:
Contract
let
l2Bridge
:
Contract
let
l1Messenger
:
Contract
let
l1Messenger
:
Contract
let
l2Messenger
:
Contract
let
l2Messenger
:
Contract
let
provider
:
CrossChainProvider
let
messenger
:
CrossChainMessenger
let
messenger
:
CrossChainMessenger
beforeEach
(
async
()
=>
{
beforeEach
(
async
()
=>
{
l1Messenger
=
(
await
(
l1Messenger
=
(
await
(
...
@@ -29,25 +933,422 @@ describe('CrossChainMessenger', () => {
...
@@ -29,25 +933,422 @@ describe('CrossChainMessenger', () => {
l2Messenger
=
(
await
(
l2Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
).
deploy
())
as
any
l1Bridge
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockBridge
'
)
).
deploy
(
l1Messenger
.
address
))
as
any
l2Bridge
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockBridge
'
)
).
deploy
(
l2Messenger
.
address
))
as
any
provider
=
new
CrossChainProvid
er
({
messenger
=
new
CrossChainMesseng
er
({
l1Provider
:
ethers
.
provider
,
l1
SignerOr
Provider
:
ethers
.
provider
,
l2Provider
:
ethers
.
provider
,
l2
SignerOr
Provider
:
ethers
.
provider
,
l1ChainId
:
31337
,
l1ChainId
:
31337
,
contracts
:
{
contracts
:
{
l1
:
{
l1
:
{
L1CrossDomainMessenger
:
l1Messenger
.
address
,
L1CrossDomainMessenger
:
l1Messenger
.
address
,
L1StandardBridge
:
l1Bridge
.
address
,
},
l2
:
{
L2CrossDomainMessenger
:
l2Messenger
.
address
,
L2StandardBridge
:
l2Bridge
.
address
,
},
},
},
})
})
describe
(
'
when the message has been relayed
'
,
()
=>
{
describe
(
'
when the relay was successful
'
,
()
=>
{
it
(
'
should return the receipt of the transaction that relayed the message
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
gasLimit
:
0
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
const
tx
=
await
l2Messenger
.
triggerRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
const
messageReceipt
=
await
messenger
.
getMessageReceipt
(
message
)
expect
(
messageReceipt
.
receiptStatus
).
to
.
equal
(
1
)
expect
(
omit
(
messageReceipt
.
transactionReceipt
,
'
confirmations
'
)
).
to
.
deep
.
equal
(
omit
(
await
ethers
.
provider
.
getTransactionReceipt
(
tx
.
hash
),
'
confirmations
'
)
)
})
})
describe
(
'
when the relay failed
'
,
()
=>
{
it
(
'
should return the receipt of the transaction that attempted to relay the message
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
gasLimit
:
0
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
const
tx
=
await
l2Messenger
.
triggerFailedRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
const
messageReceipt
=
await
messenger
.
getMessageReceipt
(
message
)
expect
(
messageReceipt
.
receiptStatus
).
to
.
equal
(
0
)
expect
(
omit
(
messageReceipt
.
transactionReceipt
,
'
confirmations
'
)
).
to
.
deep
.
equal
(
omit
(
await
ethers
.
provider
.
getTransactionReceipt
(
tx
.
hash
),
'
confirmations
'
)
)
})
})
describe
(
'
when the relay failed more than once
'
,
()
=>
{
it
(
'
should return the receipt of the last transaction that attempted to relay the message
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
gasLimit
:
0
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
await
l2Messenger
.
triggerFailedRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
const
tx
=
await
l2Messenger
.
triggerFailedRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
const
messageReceipt
=
await
messenger
.
getMessageReceipt
(
message
)
expect
(
messageReceipt
.
receiptStatus
).
to
.
equal
(
0
)
expect
(
omit
(
messageReceipt
.
transactionReceipt
,
'
confirmations
'
)
).
to
.
deep
.
equal
(
omit
(
await
ethers
.
provider
.
getTransactionReceipt
(
tx
.
hash
),
'
confirmations
'
)
)
})
})
})
describe
(
'
when the message has not been relayed
'
,
()
=>
{
it
(
'
should return null
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
gasLimit
:
0
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
await
l2Messenger
.
doNothing
()
const
messageReceipt
=
await
messenger
.
getMessageReceipt
(
message
)
expect
(
messageReceipt
).
to
.
equal
(
null
)
})
})
// TODO: Go over all of these tests and remove the empty functions so we can accurately keep
// track of
})
describe
(
'
waitForMessageReceipt
'
,
()
=>
{
let
l2Messenger
:
Contract
let
messenger
:
CrossChainMessenger
beforeEach
(
async
()
=>
{
l2Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
messenger
=
new
CrossChainMessenger
({
l1SignerOrProvider
:
ethers
.
provider
,
l2SignerOrProvider
:
ethers
.
provider
,
l1ChainId
:
31337
,
contracts
:
{
l2
:
{
l2
:
{
L2CrossDomainMessenger
:
l2Messenger
.
address
,
L2CrossDomainMessenger
:
l2Messenger
.
address
,
},
},
},
},
})
})
})
describe
(
'
when the message receipt already exists
'
,
()
=>
{
it
(
'
should immediately return the receipt
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
gasLimit
:
0
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
const
tx
=
await
l2Messenger
.
triggerRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
const
messageReceipt
=
await
messenger
.
waitForMessageReceipt
(
message
)
expect
(
messageReceipt
.
receiptStatus
).
to
.
equal
(
1
)
expect
(
omit
(
messageReceipt
.
transactionReceipt
,
'
confirmations
'
)
).
to
.
deep
.
equal
(
omit
(
await
ethers
.
provider
.
getTransactionReceipt
(
tx
.
hash
),
'
confirmations
'
)
)
})
})
describe
(
'
when the message receipt does not exist already
'
,
()
=>
{
describe
(
'
when no extra options are provided
'
,
()
=>
{
it
(
'
should wait for the receipt to be published
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
gasLimit
:
0
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
setTimeout
(
async
()
=>
{
await
l2Messenger
.
triggerRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
},
5000
)
const
tick
=
Date
.
now
()
const
messageReceipt
=
await
messenger
.
waitForMessageReceipt
(
message
)
const
tock
=
Date
.
now
()
expect
(
messageReceipt
.
receiptStatus
).
to
.
equal
(
1
)
expect
(
tock
-
tick
).
to
.
be
.
greaterThan
(
5000
)
})
it
(
'
should wait forever for the receipt if the receipt is never published
'
,
()
=>
{
// Not sure how to easily test this without introducing some sort of cancellation token
// I don't want the promise to loop forever and make the tests never finish.
})
})
describe
(
'
when a timeout is provided
'
,
()
=>
{
it
(
'
should throw an error if the timeout is reached
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
gasLimit
:
0
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
await
expect
(
messenger
.
waitForMessageReceipt
(
message
,
{
timeoutMs
:
10000
,
})
).
to
.
be
.
rejectedWith
(
'
timed out waiting for message receipt
'
)
})
})
})
})
describe
(
'
estimateL2MessageGasLimit
'
,
()
=>
{
let
messenger
:
CrossChainMessenger
beforeEach
(
async
()
=>
{
messenger
=
new
CrossChainMessenger
({
l1SignerOrProvider
:
ethers
.
provider
,
l2SignerOrProvider
:
ethers
.
provider
,
l1ChainId
:
31337
,
})
})
describe
(
'
when the message is an L1 to L2 message
'
,
()
=>
{
it
(
'
should return an accurate gas estimate plus a ~20% buffer
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
const
estimate
=
await
ethers
.
provider
.
estimateGas
({
to
:
message
.
target
,
from
:
message
.
sender
,
data
:
message
.
message
,
})
// Approximately 20% greater than the estimate, +/- 1%.
expectApprox
(
await
messenger
.
estimateL2MessageGasLimit
(
message
),
estimate
.
mul
(
120
).
div
(
100
),
{
percentUpperDeviation
:
1
,
percentLowerDeviation
:
1
,
}
)
})
it
(
'
should return an accurate gas estimate when a custom buffer is provided
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
const
estimate
=
await
ethers
.
provider
.
estimateGas
({
to
:
message
.
target
,
from
:
message
.
sender
,
data
:
message
.
message
,
})
// Approximately 30% greater than the estimate, +/- 1%.
expectApprox
(
await
messenger
.
estimateL2MessageGasLimit
(
message
,
{
bufferPercent
:
30
,
}),
estimate
.
mul
(
130
).
div
(
100
),
{
percentUpperDeviation
:
1
,
percentLowerDeviation
:
1
,
}
)
})
})
describe
(
'
when the message is an L2 to L1 message
'
,
()
=>
{
it
(
'
should throw an error
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L2_TO_L1
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
await
expect
(
messenger
.
estimateL2MessageGasLimit
(
message
)).
to
.
be
.
rejected
})
})
})
describe
(
'
estimateMessageWaitTimeBlocks
'
,
()
=>
{
describe
(
'
when the message exists
'
,
()
=>
{
describe
(
'
when the message is an L1 => L2 message
'
,
()
=>
{
describe
(
'
when the message has not been executed on L2 yet
'
,
()
=>
{
it
(
'
should return the estimated blocks until the message will be confirmed on L2
'
)
})
describe
(
'
when the message has been executed on L2
'
,
()
=>
{
it
(
'
should return 0
'
)
})
})
describe
(
'
when the message is an L2 => L1 message
'
,
()
=>
{
describe
(
'
when the state root has not been published
'
,
()
=>
{
it
(
'
should return the estimated blocks until the state root will be published and pass the challenge period
'
)
})
describe
(
'
when the state root is within the challenge period
'
,
()
=>
{
it
(
'
should return the estimated blocks until the state root passes the challenge period
'
)
})
describe
(
'
when the state root passes the challenge period
'
,
()
=>
{
it
(
'
should return 0
'
)
})
})
})
describe
(
'
when the message does not exist
'
,
()
=>
{
it
(
'
should throw an error
'
)
})
})
describe
(
'
estimateMessageWaitTimeSeconds
'
,
()
=>
{
it
(
'
should be the result of estimateMessageWaitTimeBlocks multiplied by the L1 block time
'
)
})
describe
(
'
sendMessage
'
,
()
=>
{
let
l1Messenger
:
Contract
let
l2Messenger
:
Contract
let
messenger
:
CrossChainMessenger
beforeEach
(
async
()
=>
{
l1Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
l2Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
messenger
=
new
CrossChainMessenger
({
messenger
=
new
CrossChainMessenger
({
provider
,
l1SignerOrProvider
:
l1Signer
,
l1Signer
,
l2SignerOrProvider
:
l2Signer
,
l2Signer
,
l1ChainId
:
31337
,
contracts
:
{
l1
:
{
L1CrossDomainMessenger
:
l1Messenger
.
address
,
},
l2
:
{
L2CrossDomainMessenger
:
l2Messenger
.
address
,
},
},
})
})
})
})
...
@@ -60,7 +1361,7 @@ describe('CrossChainMessenger', () => {
...
@@ -60,7 +1361,7 @@ describe('CrossChainMessenger', () => {
message
:
'
0x
'
+
'
22
'
.
repeat
(
32
),
message
:
'
0x
'
+
'
22
'
.
repeat
(
32
),
}
}
const
estimate
=
await
provid
er
.
estimateL2MessageGasLimit
(
message
)
const
estimate
=
await
messeng
er
.
estimateL2MessageGasLimit
(
message
)
await
expect
(
messenger
.
sendMessage
(
message
))
await
expect
(
messenger
.
sendMessage
(
message
))
.
to
.
emit
(
l1Messenger
,
'
SentMessage
'
)
.
to
.
emit
(
l1Messenger
,
'
SentMessage
'
)
.
withArgs
(
.
withArgs
(
...
@@ -122,7 +1423,6 @@ describe('CrossChainMessenger', () => {
...
@@ -122,7 +1423,6 @@ describe('CrossChainMessenger', () => {
describe
(
'
resendMessage
'
,
()
=>
{
describe
(
'
resendMessage
'
,
()
=>
{
let
l1Messenger
:
Contract
let
l1Messenger
:
Contract
let
l2Messenger
:
Contract
let
l2Messenger
:
Contract
let
provider
:
CrossChainProvider
let
messenger
:
CrossChainMessenger
let
messenger
:
CrossChainMessenger
beforeEach
(
async
()
=>
{
beforeEach
(
async
()
=>
{
l1Messenger
=
(
await
(
l1Messenger
=
(
await
(
...
@@ -132,9 +1432,9 @@ describe('CrossChainMessenger', () => {
...
@@ -132,9 +1432,9 @@ describe('CrossChainMessenger', () => {
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
).
deploy
())
as
any
provider
=
new
CrossChainProvid
er
({
messenger
=
new
CrossChainMesseng
er
({
l1
Provider
:
ethers
.
provid
er
,
l1
SignerOrProvider
:
l1Sign
er
,
l2
Provider
:
ethers
.
provid
er
,
l2
SignerOrProvider
:
l2Sign
er
,
l1ChainId
:
31337
,
l1ChainId
:
31337
,
contracts
:
{
contracts
:
{
l1
:
{
l1
:
{
...
@@ -145,12 +1445,6 @@ describe('CrossChainMessenger', () => {
...
@@ -145,12 +1445,6 @@ describe('CrossChainMessenger', () => {
},
},
},
},
})
})
messenger
=
new
CrossChainMessenger
({
provider
,
l1Signer
,
l2Signer
,
})
})
})
describe
(
'
when resending an L1 to L2 message
'
,
()
=>
{
describe
(
'
when resending an L1 to L2 message
'
,
()
=>
{
...
@@ -219,7 +1513,6 @@ describe('CrossChainMessenger', () => {
...
@@ -219,7 +1513,6 @@ describe('CrossChainMessenger', () => {
let
l2Messenger
:
Contract
let
l2Messenger
:
Contract
let
l1Bridge
:
Contract
let
l1Bridge
:
Contract
let
l2Bridge
:
Contract
let
l2Bridge
:
Contract
let
provider
:
CrossChainProvider
let
messenger
:
CrossChainMessenger
let
messenger
:
CrossChainMessenger
beforeEach
(
async
()
=>
{
beforeEach
(
async
()
=>
{
l1Messenger
=
(
await
(
l1Messenger
=
(
await
(
...
@@ -235,9 +1528,9 @@ describe('CrossChainMessenger', () => {
...
@@ -235,9 +1528,9 @@ describe('CrossChainMessenger', () => {
await
ethers
.
getContractFactory
(
'
MockBridge
'
)
await
ethers
.
getContractFactory
(
'
MockBridge
'
)
).
deploy
(
l2Messenger
.
address
))
as
any
).
deploy
(
l2Messenger
.
address
))
as
any
provider
=
new
CrossChainProvid
er
({
messenger
=
new
CrossChainMesseng
er
({
l1
Provider
:
ethers
.
provid
er
,
l1
SignerOrProvider
:
l1Sign
er
,
l2
Provider
:
ethers
.
provid
er
,
l2
SignerOrProvider
:
l2Sign
er
,
l1ChainId
:
31337
,
l1ChainId
:
31337
,
contracts
:
{
contracts
:
{
l1
:
{
l1
:
{
...
@@ -257,12 +1550,6 @@ describe('CrossChainMessenger', () => {
...
@@ -257,12 +1550,6 @@ describe('CrossChainMessenger', () => {
},
},
},
},
})
})
messenger
=
new
CrossChainMessenger
({
provider
,
l1Signer
,
l2Signer
,
})
})
})
it
(
'
should trigger the deposit ETH function with the given amount
'
,
async
()
=>
{
it
(
'
should trigger the deposit ETH function with the given amount
'
,
async
()
=>
{
...
@@ -282,7 +1569,6 @@ describe('CrossChainMessenger', () => {
...
@@ -282,7 +1569,6 @@ describe('CrossChainMessenger', () => {
let
l2Messenger
:
Contract
let
l2Messenger
:
Contract
let
l1Bridge
:
Contract
let
l1Bridge
:
Contract
let
l2Bridge
:
Contract
let
l2Bridge
:
Contract
let
provider
:
CrossChainProvider
let
messenger
:
CrossChainMessenger
let
messenger
:
CrossChainMessenger
beforeEach
(
async
()
=>
{
beforeEach
(
async
()
=>
{
l1Messenger
=
(
await
(
l1Messenger
=
(
await
(
...
@@ -298,9 +1584,9 @@ describe('CrossChainMessenger', () => {
...
@@ -298,9 +1584,9 @@ describe('CrossChainMessenger', () => {
await
ethers
.
getContractFactory
(
'
MockBridge
'
)
await
ethers
.
getContractFactory
(
'
MockBridge
'
)
).
deploy
(
l2Messenger
.
address
))
as
any
).
deploy
(
l2Messenger
.
address
))
as
any
provider
=
new
CrossChainProvid
er
({
messenger
=
new
CrossChainMesseng
er
({
l1
Provider
:
ethers
.
provid
er
,
l1
SignerOrProvider
:
l1Sign
er
,
l2
Provider
:
ethers
.
provid
er
,
l2
SignerOrProvider
:
l2Sign
er
,
l1ChainId
:
31337
,
l1ChainId
:
31337
,
contracts
:
{
contracts
:
{
l1
:
{
l1
:
{
...
@@ -320,15 +1606,9 @@ describe('CrossChainMessenger', () => {
...
@@ -320,15 +1606,9 @@ describe('CrossChainMessenger', () => {
},
},
},
},
})
})
messenger
=
new
CrossChainMessenger
({
provider
,
l1Signer
,
l2Signer
,
})
})
})
it
(
'
should trigger the
deposit
ETH function with the given amount
'
,
async
()
=>
{
it
(
'
should trigger the
withdraw
ETH function with the given amount
'
,
async
()
=>
{
await
expect
(
messenger
.
withdrawETH
(
100000
))
await
expect
(
messenger
.
withdrawETH
(
100000
))
.
to
.
emit
(
l2Bridge
,
'
WithdrawalInitiated
'
)
.
to
.
emit
(
l2Bridge
,
'
WithdrawalInitiated
'
)
.
withArgs
(
.
withArgs
(
...
...
packages/sdk/test/cross-chain-provider.spec.ts
deleted
100644 → 0
View file @
ceea12f3
import
{
Provider
}
from
'
@ethersproject/abstract-provider
'
import
{
expectApprox
}
from
'
@eth-optimism/core-utils
'
import
{
Contract
}
from
'
ethers
'
import
{
ethers
}
from
'
hardhat
'
import
{
expect
}
from
'
./setup
'
import
{
CrossChainProvider
,
MessageDirection
,
CONTRACT_ADDRESSES
,
hashCrossChainMessage
,
omit
,
MessageStatus
,
CrossChainMessage
,
StandardBridgeAdapter
,
}
from
'
../src
'
import
{
DUMMY_MESSAGE
}
from
'
./helpers
'
describe
(
'
CrossChainProvider
'
,
()
=>
{
describe
(
'
construction
'
,
()
=>
{
describe
(
'
when given an ethers provider for the L1 provider
'
,
()
=>
{
it
(
'
should use the provider as the L1 provider
'
,
()
=>
{
const
provider
=
new
CrossChainProvider
({
l1Provider
:
ethers
.
provider
,
l2Provider
:
ethers
.
provider
,
l1ChainId
:
1
,
})
expect
(
provider
.
l1Provider
).
to
.
equal
(
ethers
.
provider
)
})
})
describe
(
'
when given an ethers provider for the L2 provider
'
,
()
=>
{
it
(
'
should use the provider as the L2 provider
'
,
()
=>
{
const
provider
=
new
CrossChainProvider
({
l1Provider
:
ethers
.
provider
,
l2Provider
:
ethers
.
provider
,
l1ChainId
:
1
,
})
expect
(
provider
.
l2Provider
).
to
.
equal
(
ethers
.
provider
)
})
})
describe
(
'
when given a string as the L1 provider
'
,
()
=>
{
it
(
'
should create a JSON-RPC provider for the L1 provider
'
,
()
=>
{
const
provider
=
new
CrossChainProvider
({
l1Provider
:
'
https://localhost:8545
'
,
l2Provider
:
ethers
.
provider
,
l1ChainId
:
1
,
})
expect
(
Provider
.
isProvider
(
provider
.
l1Provider
)).
to
.
be
.
true
})
})
describe
(
'
when given a string as the L2 provider
'
,
()
=>
{
it
(
'
should create a JSON-RPC provider for the L2 provider
'
,
()
=>
{
const
provider
=
new
CrossChainProvider
({
l1Provider
:
ethers
.
provider
,
l2Provider
:
'
https://localhost:8545
'
,
l1ChainId
:
1
,
})
expect
(
Provider
.
isProvider
(
provider
.
l2Provider
)).
to
.
be
.
true
})
})
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
'
,
()
=>
{
const
provider
=
new
CrossChainProvider
({
l1Provider
:
ethers
.
provider
,
l2Provider
:
'
https://localhost:8545
'
,
l1ChainId
:
1
,
})
const
addresses
=
CONTRACT_ADDRESSES
[
1
]
for
(
const
[
contractName
,
contractAddress
]
of
Object
.
entries
(
addresses
.
l1
))
{
const
contract
=
provider
.
contracts
.
l1
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
contractAddress
)
}
for
(
const
[
contractName
,
contractAddress
]
of
Object
.
entries
(
addresses
.
l2
))
{
const
contract
=
provider
.
contracts
.
l2
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
contractAddress
)
}
})
})
describe
(
'
when given an unknown chain ID
'
,
()
=>
{
it
(
'
should throw an error
'
,
()
=>
{
expect
(()
=>
{
new
CrossChainProvider
({
l1Provider
:
ethers
.
provider
,
l2Provider
:
'
https://localhost:8545
'
,
l1ChainId
:
1234
,
})
}).
to
.
throw
()
})
})
})
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
'
,
()
=>
{
const
overrides
=
{
l1
:
{
L1CrossDomainMessenger
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
},
l2
:
{
L2CrossDomainMessenger
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
},
}
const
provider
=
new
CrossChainProvider
({
l1Provider
:
ethers
.
provider
,
l2Provider
:
'
https://localhost:8545
'
,
l1ChainId
:
1
,
contracts
:
overrides
,
})
const
addresses
=
CONTRACT_ADDRESSES
[
1
]
for
(
const
[
contractName
,
contractAddress
]
of
Object
.
entries
(
addresses
.
l1
))
{
if
(
overrides
.
l1
[
contractName
])
{
const
contract
=
provider
.
contracts
.
l1
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
overrides
.
l1
[
contractName
])
}
else
{
const
contract
=
provider
.
contracts
.
l1
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
contractAddress
)
}
}
for
(
const
[
contractName
,
contractAddress
]
of
Object
.
entries
(
addresses
.
l2
))
{
if
(
overrides
.
l2
[
contractName
])
{
const
contract
=
provider
.
contracts
.
l2
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
overrides
.
l2
[
contractName
])
}
else
{
const
contract
=
provider
.
contracts
.
l2
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
contractAddress
)
}
}
})
})
describe
(
'
when given an unknown chain ID
'
,
()
=>
{
describe
(
'
when all L1 addresses are provided
'
,
()
=>
{
it
(
'
should use custom addresses where provided
'
,
()
=>
{
const
overrides
=
{
l1
:
{
AddressManager
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
L1CrossDomainMessenger
:
'
0x
'
+
'
12
'
.
repeat
(
20
),
L1StandardBridge
:
'
0x
'
+
'
13
'
.
repeat
(
20
),
StateCommitmentChain
:
'
0x
'
+
'
14
'
.
repeat
(
20
),
CanonicalTransactionChain
:
'
0x
'
+
'
15
'
.
repeat
(
20
),
BondManager
:
'
0x
'
+
'
16
'
.
repeat
(
20
),
},
l2
:
{
L2CrossDomainMessenger
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
},
}
const
provider
=
new
CrossChainProvider
({
l1Provider
:
ethers
.
provider
,
l2Provider
:
'
https://localhost:8545
'
,
l1ChainId
:
1234
,
contracts
:
overrides
,
})
const
addresses
=
CONTRACT_ADDRESSES
[
1
]
for
(
const
[
contractName
,
contractAddress
]
of
Object
.
entries
(
addresses
.
l1
))
{
if
(
overrides
.
l1
[
contractName
])
{
const
contract
=
provider
.
contracts
.
l1
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
overrides
.
l1
[
contractName
])
}
else
{
const
contract
=
provider
.
contracts
.
l1
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
contractAddress
)
}
}
for
(
const
[
contractName
,
contractAddress
]
of
Object
.
entries
(
addresses
.
l2
))
{
if
(
overrides
.
l2
[
contractName
])
{
const
contract
=
provider
.
contracts
.
l2
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
overrides
.
l2
[
contractName
])
}
else
{
const
contract
=
provider
.
contracts
.
l2
[
contractName
]
expect
(
contract
.
address
).
to
.
equal
(
contractAddress
)
}
}
})
})
describe
(
'
when not all L1 addresses are provided
'
,
()
=>
{
it
(
'
should throw an error
'
,
()
=>
{
expect
(()
=>
{
new
CrossChainProvider
({
l1Provider
:
ethers
.
provider
,
l2Provider
:
'
https://localhost:8545
'
,
l1ChainId
:
1234
,
contracts
:
{
l1
:
{
// Missing some required L1 addresses
AddressManager
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
L1CrossDomainMessenger
:
'
0x
'
+
'
12
'
.
repeat
(
20
),
L1StandardBridge
:
'
0x
'
+
'
13
'
.
repeat
(
20
),
},
l2
:
{
L2CrossDomainMessenger
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
},
},
})
}).
to
.
throw
()
})
})
})
})
})
describe
(
'
getMessagesByTransaction
'
,
()
=>
{
let
l1Messenger
:
Contract
let
l2Messenger
:
Contract
let
provider
:
CrossChainProvider
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
,
},
},
})
})
describe
(
'
when a direction is specified
'
,
()
=>
{
describe
(
'
when the transaction exists
'
,
()
=>
{
describe
(
'
when the transaction has messages
'
,
()
=>
{
for
(
const
n
of
[
1
,
2
,
4
,
8
])
{
it
(
`should find
${
n
}
messages when the transaction emits
${
n
}
messages`
,
async
()
=>
{
const
messages
=
[...
Array
(
n
)].
map
(()
=>
{
return
DUMMY_MESSAGE
})
const
tx
=
await
l1Messenger
.
triggerSentMessageEvents
(
messages
)
const
found
=
await
provider
.
getMessagesByTransaction
(
tx
,
{
direction
:
MessageDirection
.
L1_TO_L2
,
})
expect
(
found
).
to
.
deep
.
equal
(
messages
.
map
((
message
,
i
)
=>
{
return
{
direction
:
MessageDirection
.
L1_TO_L2
,
sender
:
message
.
sender
,
target
:
message
.
target
,
message
:
message
.
message
,
messageNonce
:
ethers
.
BigNumber
.
from
(
message
.
messageNonce
),
gasLimit
:
ethers
.
BigNumber
.
from
(
message
.
gasLimit
),
logIndex
:
i
,
blockNumber
:
tx
.
blockNumber
,
transactionHash
:
tx
.
hash
,
}
})
)
})
}
})
describe
(
'
when the transaction has no messages
'
,
()
=>
{
it
(
'
should find nothing
'
,
async
()
=>
{
const
tx
=
await
l1Messenger
.
doNothing
()
const
found
=
await
provider
.
getMessagesByTransaction
(
tx
,
{
direction
:
MessageDirection
.
L1_TO_L2
,
})
expect
(
found
).
to
.
deep
.
equal
([])
})
})
})
describe
(
'
when the transaction does not exist in the specified direction
'
,
()
=>
{
it
(
'
should throw an error
'
,
async
()
=>
{
await
expect
(
provider
.
getMessagesByTransaction
(
'
0x
'
+
'
11
'
.
repeat
(
32
),
{
direction
:
MessageDirection
.
L1_TO_L2
,
})
).
to
.
be
.
rejectedWith
(
'
unable to find transaction receipt
'
)
})
})
})
describe
(
'
when a direction is not specified
'
,
()
=>
{
describe
(
'
when the transaction exists only on L1
'
,
()
=>
{
describe
(
'
when the transaction has messages
'
,
()
=>
{
for
(
const
n
of
[
1
,
2
,
4
,
8
])
{
it
(
`should find
${
n
}
messages when the transaction emits
${
n
}
messages`
,
async
()
=>
{
const
messages
=
[...
Array
(
n
)].
map
(()
=>
{
return
DUMMY_MESSAGE
})
const
tx
=
await
l1Messenger
.
triggerSentMessageEvents
(
messages
)
const
found
=
await
provider
.
getMessagesByTransaction
(
tx
)
expect
(
found
).
to
.
deep
.
equal
(
messages
.
map
((
message
,
i
)
=>
{
return
{
direction
:
MessageDirection
.
L1_TO_L2
,
sender
:
message
.
sender
,
target
:
message
.
target
,
message
:
message
.
message
,
messageNonce
:
ethers
.
BigNumber
.
from
(
message
.
messageNonce
),
gasLimit
:
ethers
.
BigNumber
.
from
(
message
.
gasLimit
),
logIndex
:
i
,
blockNumber
:
tx
.
blockNumber
,
transactionHash
:
tx
.
hash
,
}
})
)
})
}
})
describe
(
'
when the transaction has no messages
'
,
()
=>
{
it
(
'
should find nothing
'
,
async
()
=>
{
const
tx
=
await
l1Messenger
.
doNothing
()
const
found
=
await
provider
.
getMessagesByTransaction
(
tx
)
expect
(
found
).
to
.
deep
.
equal
([])
})
})
})
describe
(
'
when the transaction exists only on L2
'
,
()
=>
{
describe
(
'
when the transaction has messages
'
,
()
=>
{
for
(
const
n
of
[
1
,
2
,
4
,
8
])
{
it
(
`should find
${
n
}
messages when the transaction emits
${
n
}
messages`
,
()
=>
{
// TODO: Need support for simulating more than one network.
})
}
})
describe
(
'
when the transaction has no messages
'
,
()
=>
{
it
(
'
should find nothing
'
,
()
=>
{
// TODO: Need support for simulating more than one network.
})
})
})
describe
(
'
when the transaction does not exist
'
,
()
=>
{
it
(
'
should throw an error
'
,
async
()
=>
{
await
expect
(
provider
.
getMessagesByTransaction
(
'
0x
'
+
'
11
'
.
repeat
(
32
))
).
to
.
be
.
rejectedWith
(
'
unable to find transaction receipt
'
)
})
})
describe
(
'
when the transaction exists on both L1 and L2
'
,
()
=>
{
it
(
'
should throw an error
'
,
async
()
=>
{
// TODO: Need support for simulating more than one network.
})
})
})
})
describe
(
'
getMessagesByAddress
'
,
()
=>
{
describe
(
'
when the address has sent messages
'
,
()
=>
{
describe
(
'
when no direction is specified
'
,
()
=>
{
it
(
'
should find all messages sent by the address
'
)
})
describe
(
'
when a direction is specified
'
,
()
=>
{
it
(
'
should find all messages only in the given direction
'
)
})
describe
(
'
when a block range is specified
'
,
()
=>
{
it
(
'
should find all messages within the block range
'
)
})
describe
(
'
when both a direction and a block range are specified
'
,
()
=>
{
it
(
'
should find all messages only in the given direction and within the block range
'
)
})
})
describe
(
'
when the address has not sent messages
'
,
()
=>
{
it
(
'
should find nothing
'
)
})
})
describe
(
'
getTokenBridgeMessagesByAddress
'
,
()
=>
{
let
l1Bridge
:
Contract
let
l2Bridge
:
Contract
let
l1Messenger
:
Contract
let
l2Messenger
:
Contract
let
provider
:
CrossChainProvider
beforeEach
(
async
()
=>
{
l1Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
l2Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
l1Bridge
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockBridge
'
)
).
deploy
(
l1Messenger
.
address
))
as
any
l2Bridge
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockBridge
'
)
).
deploy
(
l2Messenger
.
address
))
as
any
provider
=
new
CrossChainProvider
({
l1Provider
:
ethers
.
provider
,
l2Provider
:
ethers
.
provider
,
l1ChainId
:
31337
,
contracts
:
{
l1
:
{
L1CrossDomainMessenger
:
l1Messenger
.
address
,
L1StandardBridge
:
l1Bridge
.
address
,
},
l2
:
{
L2CrossDomainMessenger
:
l2Messenger
.
address
,
L2StandardBridge
:
l2Bridge
.
address
,
},
},
bridges
:
{
Standard
:
{
Adapter
:
StandardBridgeAdapter
,
l1Bridge
:
l1Bridge
.
address
,
l2Bridge
:
l2Bridge
.
address
,
},
},
})
})
describe
(
'
when the address has made deposits or withdrawals
'
,
()
=>
{
describe
(
'
when a direction of L1 => L2 is specified
'
,
()
=>
{
it
(
'
should find all deposits made by the address
'
,
async
()
=>
{
const
from
=
'
0x
'
+
'
99
'
.
repeat
(
20
)
const
deposit
=
{
l1Token
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
l2Token
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
from
,
to
:
'
0x
'
+
'
44
'
.
repeat
(
20
),
amount
:
ethers
.
BigNumber
.
from
(
1234
),
data
:
'
0x1234
'
,
}
const
withdrawal
=
{
l1Token
:
'
0x
'
+
'
12
'
.
repeat
(
20
),
l2Token
:
'
0x
'
+
'
23
'
.
repeat
(
20
),
from
,
to
:
'
0x
'
+
'
45
'
.
repeat
(
20
),
amount
:
ethers
.
BigNumber
.
from
(
5678
),
data
:
'
0x5678
'
,
}
await
l1Bridge
.
emitERC20DepositInitiated
(
deposit
)
await
l2Bridge
.
emitWithdrawalInitiated
(
withdrawal
)
const
found
=
await
provider
.
getTokenBridgeMessagesByAddress
(
from
,
{
direction
:
MessageDirection
.
L1_TO_L2
,
})
expect
(
found
.
length
).
to
.
equal
(
1
)
expect
(
found
[
0
].
amount
).
to
.
deep
.
equal
(
deposit
.
amount
)
expect
(
found
[
0
].
data
).
to
.
deep
.
equal
(
deposit
.
data
)
expect
(
found
[
0
].
direction
).
to
.
equal
(
MessageDirection
.
L1_TO_L2
)
expect
(
found
[
0
].
l1Token
).
to
.
deep
.
equal
(
deposit
.
l1Token
)
expect
(
found
[
0
].
l2Token
).
to
.
deep
.
equal
(
deposit
.
l2Token
)
expect
(
found
[
0
].
from
).
to
.
deep
.
equal
(
deposit
.
from
)
expect
(
found
[
0
].
to
).
to
.
deep
.
equal
(
deposit
.
to
)
})
})
describe
(
'
when a direction of L2 => L1 is specified
'
,
()
=>
{
it
(
'
should find all withdrawals made by the address
'
,
async
()
=>
{
const
from
=
'
0x
'
+
'
99
'
.
repeat
(
20
)
const
deposit
=
{
l1Token
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
l2Token
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
from
,
to
:
'
0x
'
+
'
44
'
.
repeat
(
20
),
amount
:
ethers
.
BigNumber
.
from
(
1234
),
data
:
'
0x1234
'
,
}
const
withdrawal
=
{
l1Token
:
'
0x
'
+
'
12
'
.
repeat
(
20
),
l2Token
:
'
0x
'
+
'
23
'
.
repeat
(
20
),
from
,
to
:
'
0x
'
+
'
45
'
.
repeat
(
20
),
amount
:
ethers
.
BigNumber
.
from
(
5678
),
data
:
'
0x5678
'
,
}
await
l1Bridge
.
emitERC20DepositInitiated
(
deposit
)
await
l2Bridge
.
emitWithdrawalInitiated
(
withdrawal
)
const
found
=
await
provider
.
getTokenBridgeMessagesByAddress
(
from
,
{
direction
:
MessageDirection
.
L2_TO_L1
,
})
expect
(
found
.
length
).
to
.
equal
(
1
)
expect
(
found
[
0
].
amount
).
to
.
deep
.
equal
(
withdrawal
.
amount
)
expect
(
found
[
0
].
data
).
to
.
deep
.
equal
(
withdrawal
.
data
)
expect
(
found
[
0
].
direction
).
to
.
equal
(
MessageDirection
.
L2_TO_L1
)
expect
(
found
[
0
].
l1Token
).
to
.
deep
.
equal
(
withdrawal
.
l1Token
)
expect
(
found
[
0
].
l2Token
).
to
.
deep
.
equal
(
withdrawal
.
l2Token
)
expect
(
found
[
0
].
from
).
to
.
deep
.
equal
(
withdrawal
.
from
)
expect
(
found
[
0
].
to
).
to
.
deep
.
equal
(
withdrawal
.
to
)
})
})
describe
(
'
when no direction is specified
'
,
()
=>
{
it
(
'
should find all deposits and withdrawals made by the address
'
,
async
()
=>
{
const
from
=
'
0x
'
+
'
99
'
.
repeat
(
20
)
const
deposit
=
{
l1Token
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
l2Token
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
from
,
to
:
'
0x
'
+
'
44
'
.
repeat
(
20
),
amount
:
ethers
.
BigNumber
.
from
(
1234
),
data
:
'
0x1234
'
,
}
const
withdrawal
=
{
l1Token
:
'
0x
'
+
'
12
'
.
repeat
(
20
),
l2Token
:
'
0x
'
+
'
23
'
.
repeat
(
20
),
from
,
to
:
'
0x
'
+
'
45
'
.
repeat
(
20
),
amount
:
ethers
.
BigNumber
.
from
(
5678
),
data
:
'
0x5678
'
,
}
await
l1Bridge
.
emitERC20DepositInitiated
(
deposit
)
await
l2Bridge
.
emitWithdrawalInitiated
(
withdrawal
)
const
found
=
await
provider
.
getTokenBridgeMessagesByAddress
(
from
)
expect
(
found
.
length
).
to
.
equal
(
2
)
// Check the deposit (deposits get searched first)
expect
(
found
[
0
].
amount
).
to
.
deep
.
equal
(
deposit
.
amount
)
expect
(
found
[
0
].
data
).
to
.
deep
.
equal
(
deposit
.
data
)
expect
(
found
[
0
].
direction
).
to
.
equal
(
MessageDirection
.
L1_TO_L2
)
expect
(
found
[
0
].
l1Token
).
to
.
deep
.
equal
(
deposit
.
l1Token
)
expect
(
found
[
0
].
l2Token
).
to
.
deep
.
equal
(
deposit
.
l2Token
)
expect
(
found
[
0
].
from
).
to
.
deep
.
equal
(
deposit
.
from
)
expect
(
found
[
0
].
to
).
to
.
deep
.
equal
(
deposit
.
to
)
// Check the withdrawal
expect
(
found
[
1
].
amount
).
to
.
deep
.
equal
(
withdrawal
.
amount
)
expect
(
found
[
1
].
data
).
to
.
deep
.
equal
(
withdrawal
.
data
)
expect
(
found
[
1
].
direction
).
to
.
equal
(
MessageDirection
.
L2_TO_L1
)
expect
(
found
[
1
].
l1Token
).
to
.
deep
.
equal
(
withdrawal
.
l1Token
)
expect
(
found
[
1
].
l2Token
).
to
.
deep
.
equal
(
withdrawal
.
l2Token
)
expect
(
found
[
1
].
from
).
to
.
deep
.
equal
(
withdrawal
.
from
)
expect
(
found
[
1
].
to
).
to
.
deep
.
equal
(
withdrawal
.
to
)
})
})
})
describe
(
'
when the address has not made any deposits or withdrawals
'
,
()
=>
{
it
(
'
should find nothing
'
,
async
()
=>
{
const
from
=
'
0x
'
+
'
99
'
.
repeat
(
20
)
const
found
=
await
provider
.
getTokenBridgeMessagesByAddress
(
from
)
expect
(
found
).
to
.
deep
.
equal
([])
})
})
})
describe
(
'
toCrossChainMessage
'
,
()
=>
{
let
l1Bridge
:
Contract
let
l2Bridge
:
Contract
let
l1Messenger
:
Contract
let
l2Messenger
:
Contract
let
provider
:
CrossChainProvider
beforeEach
(
async
()
=>
{
l1Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
l2Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
l1Bridge
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockBridge
'
)
).
deploy
(
l1Messenger
.
address
))
as
any
l2Bridge
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockBridge
'
)
).
deploy
(
l2Messenger
.
address
))
as
any
provider
=
new
CrossChainProvider
({
l1Provider
:
ethers
.
provider
,
l2Provider
:
ethers
.
provider
,
l1ChainId
:
31337
,
contracts
:
{
l1
:
{
L1CrossDomainMessenger
:
l1Messenger
.
address
,
L1StandardBridge
:
l1Bridge
.
address
,
},
l2
:
{
L2CrossDomainMessenger
:
l2Messenger
.
address
,
L2StandardBridge
:
l2Bridge
.
address
,
},
},
bridges
:
{
Standard
:
{
Adapter
:
StandardBridgeAdapter
,
l1Bridge
:
l1Bridge
.
address
,
l2Bridge
:
l2Bridge
.
address
,
},
},
})
})
describe
(
'
when the input is a CrossChainMessage
'
,
()
=>
{
it
(
'
should return the input
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
gasLimit
:
0
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
expect
(
await
provider
.
toCrossChainMessage
(
message
)).
to
.
deep
.
equal
(
message
)
})
})
describe
(
'
when the input is a TokenBridgeMessage
'
,
()
=>
{
// TODO: There are some edge cases here with custom bridges that conform to the interface but
// not to the behavioral spec. Possibly worth testing those. For now this is probably
// sufficient.
it
(
'
should return the sent message event that came after the deposit or withdrawal
'
,
async
()
=>
{
const
from
=
'
0x
'
+
'
99
'
.
repeat
(
20
)
const
deposit
=
{
l1Token
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
l2Token
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
from
,
to
:
'
0x
'
+
'
44
'
.
repeat
(
20
),
amount
:
ethers
.
BigNumber
.
from
(
1234
),
data
:
'
0x1234
'
,
}
const
tx
=
await
l1Bridge
.
emitERC20DepositInitiated
(
deposit
)
const
foundCrossChainMessages
=
await
provider
.
getMessagesByTransaction
(
tx
)
const
foundTokenBridgeMessages
=
await
provider
.
getTokenBridgeMessagesByAddress
(
from
)
const
resolved
=
await
provider
.
toCrossChainMessage
(
foundTokenBridgeMessages
[
0
]
)
expect
(
resolved
).
to
.
deep
.
equal
(
foundCrossChainMessages
[
0
])
})
})
describe
(
'
when the input is a TransactionLike
'
,
()
=>
{
describe
(
'
when the transaction sent exactly one message
'
,
()
=>
{
it
(
'
should return the CrossChainMessage sent in the transaction
'
,
async
()
=>
{
const
tx
=
await
l1Messenger
.
triggerSentMessageEvents
([
DUMMY_MESSAGE
])
const
foundCrossChainMessages
=
await
provider
.
getMessagesByTransaction
(
tx
)
const
resolved
=
await
provider
.
toCrossChainMessage
(
tx
)
expect
(
resolved
).
to
.
deep
.
equal
(
foundCrossChainMessages
[
0
])
})
})
describe
(
'
when the transaction sent more than one message
'
,
()
=>
{
it
(
'
should throw an error
'
,
async
()
=>
{
const
messages
=
[...
Array
(
2
)].
map
(()
=>
{
return
DUMMY_MESSAGE
})
const
tx
=
await
l1Messenger
.
triggerSentMessageEvents
(
messages
)
await
expect
(
provider
.
toCrossChainMessage
(
tx
)).
to
.
be
.
rejectedWith
(
'
expected 1 message, got 2
'
)
})
})
describe
(
'
when the transaction sent no messages
'
,
()
=>
{
it
(
'
should throw an error
'
,
async
()
=>
{
const
tx
=
await
l1Messenger
.
triggerSentMessageEvents
([])
await
expect
(
provider
.
toCrossChainMessage
(
tx
)).
to
.
be
.
rejectedWith
(
'
expected 1 message, got 0
'
)
})
})
})
})
describe
(
'
getMessageStatus
'
,
()
=>
{
let
scc
:
Contract
let
l1Messenger
:
Contract
let
l2Messenger
:
Contract
let
provider
:
CrossChainProvider
beforeEach
(
async
()
=>
{
// TODO: Get rid of the nested awaits here. Could be a good first issue for someone.
scc
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockSCC
'
)).
deploy
())
as
any
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
,
StateCommitmentChain
:
scc
.
address
,
},
l2
:
{
L2CrossDomainMessenger
:
l2Messenger
.
address
,
},
},
})
})
const
sendAndGetDummyMessage
=
async
(
direction
:
MessageDirection
)
=>
{
const
messenger
=
direction
===
MessageDirection
.
L1_TO_L2
?
l1Messenger
:
l2Messenger
const
tx
=
await
messenger
.
triggerSentMessageEvents
([
DUMMY_MESSAGE
])
return
(
await
provider
.
getMessagesByTransaction
(
tx
,
{
direction
,
})
)[
0
]
}
const
submitStateRootBatchForMessage
=
async
(
message
:
CrossChainMessage
)
=>
{
await
scc
.
setSBAParams
({
batchIndex
:
0
,
batchRoot
:
ethers
.
constants
.
HashZero
,
batchSize
:
1
,
prevTotalElements
:
message
.
blockNumber
,
extraData
:
'
0x
'
,
})
await
scc
.
appendStateBatch
([
ethers
.
constants
.
HashZero
],
0
)
}
describe
(
'
when the message is an L1 => L2 message
'
,
()
=>
{
describe
(
'
when the message has not been executed on L2 yet
'
,
()
=>
{
it
(
'
should return a status of UNCONFIRMED_L1_TO_L2_MESSAGE
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L1_TO_L2
)
expect
(
await
provider
.
getMessageStatus
(
message
)).
to
.
equal
(
MessageStatus
.
UNCONFIRMED_L1_TO_L2_MESSAGE
)
})
})
describe
(
'
when the message has been executed on L2
'
,
()
=>
{
it
(
'
should return a status of RELAYED
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L1_TO_L2
)
await
l2Messenger
.
triggerRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
expect
(
await
provider
.
getMessageStatus
(
message
)).
to
.
equal
(
MessageStatus
.
RELAYED
)
})
})
describe
(
'
when the message has been executed but failed
'
,
()
=>
{
it
(
'
should return a status of FAILED_L1_TO_L2_MESSAGE
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L1_TO_L2
)
await
l2Messenger
.
triggerFailedRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
expect
(
await
provider
.
getMessageStatus
(
message
)).
to
.
equal
(
MessageStatus
.
FAILED_L1_TO_L2_MESSAGE
)
})
})
})
describe
(
'
when the message is an L2 => L1 message
'
,
()
=>
{
describe
(
'
when the message state root has not been published
'
,
()
=>
{
it
(
'
should return a status of STATE_ROOT_NOT_PUBLISHED
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L2_TO_L1
)
expect
(
await
provider
.
getMessageStatus
(
message
)).
to
.
equal
(
MessageStatus
.
STATE_ROOT_NOT_PUBLISHED
)
})
})
describe
(
'
when the message state root is still in the challenge period
'
,
()
=>
{
it
(
'
should return a status of IN_CHALLENGE_PERIOD
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L2_TO_L1
)
await
submitStateRootBatchForMessage
(
message
)
expect
(
await
provider
.
getMessageStatus
(
message
)).
to
.
equal
(
MessageStatus
.
IN_CHALLENGE_PERIOD
)
})
})
describe
(
'
when the message is no longer in the challenge period
'
,
()
=>
{
describe
(
'
when the message has been relayed successfully
'
,
()
=>
{
it
(
'
should return a status of RELAYED
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L2_TO_L1
)
await
submitStateRootBatchForMessage
(
message
)
const
challengePeriod
=
await
provider
.
getChallengePeriodSeconds
()
ethers
.
provider
.
send
(
'
evm_increaseTime
'
,
[
challengePeriod
+
1
])
ethers
.
provider
.
send
(
'
evm_mine
'
,
[])
await
l1Messenger
.
triggerRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
expect
(
await
provider
.
getMessageStatus
(
message
)).
to
.
equal
(
MessageStatus
.
RELAYED
)
})
})
describe
(
'
when the message has been relayed but the relay failed
'
,
()
=>
{
it
(
'
should return a status of READY_FOR_RELAY
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L2_TO_L1
)
await
submitStateRootBatchForMessage
(
message
)
const
challengePeriod
=
await
provider
.
getChallengePeriodSeconds
()
ethers
.
provider
.
send
(
'
evm_increaseTime
'
,
[
challengePeriod
+
1
])
ethers
.
provider
.
send
(
'
evm_mine
'
,
[])
await
l1Messenger
.
triggerFailedRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
expect
(
await
provider
.
getMessageStatus
(
message
)).
to
.
equal
(
MessageStatus
.
READY_FOR_RELAY
)
})
})
describe
(
'
when the message has not been relayed
'
,
()
=>
{
it
(
'
should return a status of READY_FOR_RELAY
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L2_TO_L1
)
await
submitStateRootBatchForMessage
(
message
)
const
challengePeriod
=
await
provider
.
getChallengePeriodSeconds
()
ethers
.
provider
.
send
(
'
evm_increaseTime
'
,
[
challengePeriod
+
1
])
ethers
.
provider
.
send
(
'
evm_mine
'
,
[])
expect
(
await
provider
.
getMessageStatus
(
message
)).
to
.
equal
(
MessageStatus
.
READY_FOR_RELAY
)
})
})
})
})
describe
(
'
when the message does not exist
'
,
()
=>
{
// TODO: Figure out if this is the correct behavior. Mark suggests perhaps returning null.
it
(
'
should throw an error
'
)
})
})
describe
(
'
getMessageReceipt
'
,
()
=>
{
let
l1Bridge
:
Contract
let
l2Bridge
:
Contract
let
l1Messenger
:
Contract
let
l2Messenger
:
Contract
let
provider
:
CrossChainProvider
beforeEach
(
async
()
=>
{
l1Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
l2Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
l1Bridge
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockBridge
'
)
).
deploy
(
l1Messenger
.
address
))
as
any
l2Bridge
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockBridge
'
)
).
deploy
(
l2Messenger
.
address
))
as
any
provider
=
new
CrossChainProvider
({
l1Provider
:
ethers
.
provider
,
l2Provider
:
ethers
.
provider
,
l1ChainId
:
31337
,
contracts
:
{
l1
:
{
L1CrossDomainMessenger
:
l1Messenger
.
address
,
L1StandardBridge
:
l1Bridge
.
address
,
},
l2
:
{
L2CrossDomainMessenger
:
l2Messenger
.
address
,
L2StandardBridge
:
l2Bridge
.
address
,
},
},
})
})
describe
(
'
when the message has been relayed
'
,
()
=>
{
describe
(
'
when the relay was successful
'
,
()
=>
{
it
(
'
should return the receipt of the transaction that relayed the message
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
gasLimit
:
0
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
const
tx
=
await
l2Messenger
.
triggerRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
const
messageReceipt
=
await
provider
.
getMessageReceipt
(
message
)
expect
(
messageReceipt
.
receiptStatus
).
to
.
equal
(
1
)
expect
(
omit
(
messageReceipt
.
transactionReceipt
,
'
confirmations
'
)
).
to
.
deep
.
equal
(
omit
(
await
ethers
.
provider
.
getTransactionReceipt
(
tx
.
hash
),
'
confirmations
'
)
)
})
})
describe
(
'
when the relay failed
'
,
()
=>
{
it
(
'
should return the receipt of the transaction that attempted to relay the message
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
gasLimit
:
0
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
const
tx
=
await
l2Messenger
.
triggerFailedRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
const
messageReceipt
=
await
provider
.
getMessageReceipt
(
message
)
expect
(
messageReceipt
.
receiptStatus
).
to
.
equal
(
0
)
expect
(
omit
(
messageReceipt
.
transactionReceipt
,
'
confirmations
'
)
).
to
.
deep
.
equal
(
omit
(
await
ethers
.
provider
.
getTransactionReceipt
(
tx
.
hash
),
'
confirmations
'
)
)
})
})
describe
(
'
when the relay failed more than once
'
,
()
=>
{
it
(
'
should return the receipt of the last transaction that attempted to relay the message
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
gasLimit
:
0
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
await
l2Messenger
.
triggerFailedRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
const
tx
=
await
l2Messenger
.
triggerFailedRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
const
messageReceipt
=
await
provider
.
getMessageReceipt
(
message
)
expect
(
messageReceipt
.
receiptStatus
).
to
.
equal
(
0
)
expect
(
omit
(
messageReceipt
.
transactionReceipt
,
'
confirmations
'
)
).
to
.
deep
.
equal
(
omit
(
await
ethers
.
provider
.
getTransactionReceipt
(
tx
.
hash
),
'
confirmations
'
)
)
})
})
})
describe
(
'
when the message has not been relayed
'
,
()
=>
{
it
(
'
should return null
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
gasLimit
:
0
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
await
l2Messenger
.
doNothing
()
const
messageReceipt
=
await
provider
.
getMessageReceipt
(
message
)
expect
(
messageReceipt
).
to
.
equal
(
null
)
})
})
// TODO: Go over all of these tests and remove the empty functions so we can accurately keep
// track of
})
describe
(
'
waitForMessageReceipt
'
,
()
=>
{
let
l2Messenger
:
Contract
let
provider
:
CrossChainProvider
beforeEach
(
async
()
=>
{
l2Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
provider
=
new
CrossChainProvider
({
l1Provider
:
ethers
.
provider
,
l2Provider
:
ethers
.
provider
,
l1ChainId
:
31337
,
contracts
:
{
l2
:
{
L2CrossDomainMessenger
:
l2Messenger
.
address
,
},
},
})
})
describe
(
'
when the message receipt already exists
'
,
()
=>
{
it
(
'
should immediately return the receipt
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
gasLimit
:
0
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
const
tx
=
await
l2Messenger
.
triggerRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
const
messageReceipt
=
await
provider
.
waitForMessageReceipt
(
message
)
expect
(
messageReceipt
.
receiptStatus
).
to
.
equal
(
1
)
expect
(
omit
(
messageReceipt
.
transactionReceipt
,
'
confirmations
'
)
).
to
.
deep
.
equal
(
omit
(
await
ethers
.
provider
.
getTransactionReceipt
(
tx
.
hash
),
'
confirmations
'
)
)
})
})
describe
(
'
when the message receipt does not exist already
'
,
()
=>
{
describe
(
'
when no extra options are provided
'
,
()
=>
{
it
(
'
should wait for the receipt to be published
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
gasLimit
:
0
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
setTimeout
(
async
()
=>
{
await
l2Messenger
.
triggerRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
},
5000
)
const
tick
=
Date
.
now
()
const
messageReceipt
=
await
provider
.
waitForMessageReceipt
(
message
)
const
tock
=
Date
.
now
()
expect
(
messageReceipt
.
receiptStatus
).
to
.
equal
(
1
)
expect
(
tock
-
tick
).
to
.
be
.
greaterThan
(
5000
)
})
it
(
'
should wait forever for the receipt if the receipt is never published
'
,
()
=>
{
// Not sure how to easily test this without introducing some sort of cancellation token
// I don't want the promise to loop forever and make the tests never finish.
})
})
describe
(
'
when a timeout is provided
'
,
()
=>
{
it
(
'
should throw an error if the timeout is reached
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
gasLimit
:
0
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
await
expect
(
provider
.
waitForMessageReceipt
(
message
,
{
timeoutMs
:
10000
,
})
).
to
.
be
.
rejectedWith
(
'
timed out waiting for message receipt
'
)
})
})
})
})
describe
(
'
estimateL2MessageGasLimit
'
,
()
=>
{
let
provider
:
CrossChainProvider
beforeEach
(
async
()
=>
{
provider
=
new
CrossChainProvider
({
l1Provider
:
ethers
.
provider
,
l2Provider
:
ethers
.
provider
,
l1ChainId
:
31337
,
})
})
describe
(
'
when the message is an L1 to L2 message
'
,
()
=>
{
it
(
'
should return an accurate gas estimate plus a ~20% buffer
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
const
estimate
=
await
ethers
.
provider
.
estimateGas
({
to
:
message
.
target
,
from
:
message
.
sender
,
data
:
message
.
message
,
})
// Approximately 20% greater than the estimate, +/- 1%.
expectApprox
(
await
provider
.
estimateL2MessageGasLimit
(
message
),
estimate
.
mul
(
120
).
div
(
100
),
{
percentUpperDeviation
:
1
,
percentLowerDeviation
:
1
,
}
)
})
it
(
'
should return an accurate gas estimate when a custom buffer is provided
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L1_TO_L2
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
const
estimate
=
await
ethers
.
provider
.
estimateGas
({
to
:
message
.
target
,
from
:
message
.
sender
,
data
:
message
.
message
,
})
// Approximately 30% greater than the estimate, +/- 1%.
expectApprox
(
await
provider
.
estimateL2MessageGasLimit
(
message
,
{
bufferPercent
:
30
,
}),
estimate
.
mul
(
130
).
div
(
100
),
{
percentUpperDeviation
:
1
,
percentLowerDeviation
:
1
,
}
)
})
})
describe
(
'
when the message is an L2 to L1 message
'
,
()
=>
{
it
(
'
should throw an error
'
,
async
()
=>
{
const
message
=
{
direction
:
MessageDirection
.
L2_TO_L1
,
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
logIndex
:
0
,
blockNumber
:
1234
,
transactionHash
:
'
0x
'
+
'
44
'
.
repeat
(
32
),
}
await
expect
(
provider
.
estimateL2MessageGasLimit
(
message
)).
to
.
be
.
rejected
})
})
})
describe
(
'
estimateMessageWaitTimeBlocks
'
,
()
=>
{
describe
(
'
when the message exists
'
,
()
=>
{
describe
(
'
when the message is an L1 => L2 message
'
,
()
=>
{
describe
(
'
when the message has not been executed on L2 yet
'
,
()
=>
{
it
(
'
should return the estimated blocks until the message will be confirmed on L2
'
)
})
describe
(
'
when the message has been executed on L2
'
,
()
=>
{
it
(
'
should return 0
'
)
})
})
describe
(
'
when the message is an L2 => L1 message
'
,
()
=>
{
describe
(
'
when the state root has not been published
'
,
()
=>
{
it
(
'
should return the estimated blocks until the state root will be published and pass the challenge period
'
)
})
describe
(
'
when the state root is within the challenge period
'
,
()
=>
{
it
(
'
should return the estimated blocks until the state root passes the challenge period
'
)
})
describe
(
'
when the state root passes the challenge period
'
,
()
=>
{
it
(
'
should return 0
'
)
})
})
})
describe
(
'
when the message does not exist
'
,
()
=>
{
it
(
'
should throw an error
'
)
})
})
describe
(
'
estimateMessageWaitTimeSeconds
'
,
()
=>
{
it
(
'
should be the result of estimateMessageWaitTimeBlocks multiplied by the L1 block time
'
)
})
})
packages/sdk/test/utils/coercion.spec.ts
View file @
77bd8dfc
...
@@ -3,17 +3,17 @@ import { Contract } from 'ethers'
...
@@ -3,17 +3,17 @@ import { Contract } from 'ethers'
import
{
ethers
}
from
'
hardhat
'
import
{
ethers
}
from
'
hardhat
'
import
{
expect
}
from
'
../setup
'
import
{
expect
}
from
'
../setup
'
import
{
toProvider
,
toTransactionHash
}
from
'
../../src
'
import
{
to
SignerOr
Provider
,
toTransactionHash
}
from
'
../../src
'
describe
(
'
type coercion utils
'
,
()
=>
{
describe
(
'
type coercion utils
'
,
()
=>
{
describe
(
'
toProvider
'
,
()
=>
{
describe
(
'
to
SignerOr
Provider
'
,
()
=>
{
it
(
'
should convert a string to a JsonRpcProvider
'
,
()
=>
{
it
(
'
should convert a string to a JsonRpcProvider
'
,
()
=>
{
const
provider
=
toProvider
(
'
http://localhost:8545
'
)
const
provider
=
to
SignerOr
Provider
(
'
http://localhost:8545
'
)
expect
(
Provider
.
isProvider
(
provider
)).
to
.
be
.
true
expect
(
Provider
.
isProvider
(
provider
)).
to
.
be
.
true
})
})
it
(
'
should not do anything with a provider
'
,
()
=>
{
it
(
'
should not do anything with a provider
'
,
()
=>
{
const
provider
=
toProvider
(
ethers
.
provider
)
const
provider
=
to
SignerOr
Provider
(
ethers
.
provider
)
expect
(
provider
).
to
.
deep
.
equal
(
ethers
.
provider
)
expect
(
provider
).
to
.
deep
.
equal
(
ethers
.
provider
)
})
})
})
})
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment