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
c8a68b7d
Unverified
Commit
c8a68b7d
authored
Dec 21, 2021
by
Kelvin Fichter
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: implement getMessageReceipt and tests
parent
9f970f0c
Changes
6
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
416 additions
and
10 deletions
+416
-10
cross-chain-provider.ts
packages/sdk/src/cross-chain-provider.ts
+108
-1
cross-chain-provider.ts
packages/sdk/src/interfaces/cross-chain-provider.ts
+12
-0
types.ts
packages/sdk/src/interfaces/types.ts
+1
-2
index.ts
packages/sdk/src/utils/index.ts
+1
-0
misc-utils.ts
packages/sdk/src/utils/misc-utils.ts
+17
-0
cross-chain-provider.spec.ts
packages/sdk/test/cross-chain-provider.spec.ts
+277
-7
No files found.
packages/sdk/src/cross-chain-provider.ts
View file @
c8a68b7d
...
...
@@ -19,6 +19,7 @@ import {
MessageStatus
,
TokenBridgeMessage
,
MessageReceipt
,
MessageReceiptStatus
,
CustomBridges
,
CustomBridgesLike
,
}
from
'
./interfaces
'
...
...
@@ -29,6 +30,7 @@ import {
DeepPartial
,
getAllOEContracts
,
getCustomBridges
,
hashCrossChainMessage
,
}
from
'
./utils
'
export
class
CrossChainProvider
implements
ICrossChainProvider
{
...
...
@@ -278,6 +280,59 @@ export class CrossChainProvider implements ICrossChainProvider {
})
}
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
>
{
throw
new
Error
(
'
Not implemented
'
)
}
...
...
@@ -285,7 +340,59 @@ export class CrossChainProvider implements ICrossChainProvider {
public
async
getMessageReceipt
(
message
:
MessageLike
):
Promise
<
MessageReceipt
>
{
throw
new
Error
(
'
Not implemented
'
)
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
waitForMessageReciept
(
...
...
packages/sdk/src/interfaces/cross-chain-provider.ts
View file @
c8a68b7d
...
...
@@ -145,6 +145,18 @@ export interface ICrossChainProvider {
}
):
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.
*
...
...
packages/sdk/src/interfaces/types.ts
View file @
c8a68b7d
...
...
@@ -188,15 +188,14 @@ export interface TokenBridgeMessage {
* Enum describing the status of a CrossDomainMessage message receipt.
*/
export
enum
MessageReceiptStatus
{
RELAYED_SUCCEEDED
,
RELAYED_FAILED
,
RELAYED_SUCCEEDED
,
}
/**
* CrossDomainMessage receipt.
*/
export
interface
MessageReceipt
{
messageHash
:
string
receiptStatus
:
MessageReceiptStatus
transactionReceipt
:
TransactionReceipt
}
...
...
packages/sdk/src/utils/index.ts
View file @
c8a68b7d
...
...
@@ -2,3 +2,4 @@ export * from './coercion'
export
*
from
'
./contracts
'
export
*
from
'
./message-encoding
'
export
*
from
'
./type-utils
'
export
*
from
'
./misc-utils
'
packages/sdk/src/utils/misc-utils.ts
0 → 100644
View file @
c8a68b7d
// TODO: A lot of this stuff could probably live in core-utils instead.
// Review this file eventually for stuff that could go into core-utils.
/**
* Returns a copy of the given object ({ ...obj }) with the given keys omitted.
*
* @param obj Object to return with the keys omitted.
* @param keys Keys to omit from the returned object.
* @returns A copy of the given object with the given keys omitted.
*/
export
const
omit
=
(
obj
:
any
,
...
keys
:
string
[])
=>
{
const
copy
=
{
...
obj
}
for
(
const
key
of
keys
)
{
delete
copy
[
key
]
}
return
copy
}
packages/sdk/test/cross-chain-provider.spec.ts
View file @
c8a68b7d
This diff is collapsed.
Click to expand it.
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