Skip to content

Commit

Permalink
fix: Better NEAR receipt parsing.
Browse files Browse the repository at this point in the history
  • Loading branch information
paouvrard committed Jan 2, 2024
1 parent 81c7056 commit 60af96c
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 457 deletions.
7 changes: 7 additions & 0 deletions .yarn/versions/bf801d06.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
releases:
"@near-eth/aurora-erc20": patch
"@near-eth/aurora-ether": patch
"@near-eth/near-ether": patch
"@near-eth/nep141-erc20": patch
"@near-eth/rainbow": patch
rainbow-bridge-client-monorepo: patch
111 changes: 43 additions & 68 deletions packages/aurora-erc20/src/bridged-erc20/sendToEthereum/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,42 +196,50 @@ export async function checkStatus (transfer: Transfer): Promise<Transfer> {
* Parse the burn receipt id and block height needed to complete
* the step BURN
* @param nearBurnTx
* @param nep141Factory
* @param nearProvider
*/
export async function parseBurnReceipt (
nearBurnTx: FinalExecutionOutcome,
nep141Factory: string,
nearProvider: najProviders.Provider
): Promise<{id: string, blockHeight: number }> {
const receiptIds = nearBurnTx.transaction_outcome.outcome.receipt_ids

if (receiptIds.length !== 1) {
throw new TransferError(
`Burn expects only one receipt, got ${receiptIds.length}.
Full withdrawal transaction: ${JSON.stringify(nearBurnTx)}`
)
): Promise<{id: string, blockHeight: number, blockTimestamp: number, event: { amount: string, token: string, recipient: string }}> {
// @ts-expect-error
const bridgeReceipt: any = nearBurnTx.receipts_outcome.find(r => r.outcome.executor_id === nep141Factory)
if (!bridgeReceipt) {
throw new Error(`Failed to parse bridge receipt for ${JSON.stringify(nearBurnTx)}`)
}
const successValue = bridgeReceipt.outcome.status.SuccessValue
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
class BurnEvent {
constructor (args: any) {
Object.assign(this, args)
}
}
const SCHEMA = new Map([
[BurnEvent, {
kind: 'struct',
fields: [
['flag', 'u8'],
['amount', 'u128'],
['token', [20]],
['recipient', [20]]
]
}]
])
const rawEvent = deserializeBorsh(
SCHEMA, BurnEvent, Buffer.from(successValue, 'base64')
) as { amount: BN, token: Uint8Array, recipient: Uint8Array }
const event = {
amount: rawEvent.amount.toString(),
token: '0x' + Buffer.from(rawEvent.token).toString('hex'),
recipient: '0x' + Buffer.from(rawEvent.recipient).toString('hex')
}

// Get receipt information for recording and building burn proof
const txReceiptId = receiptIds[0]
const successReceiptOutcome = nearBurnTx.receipts_outcome
.find(r => r.id === txReceiptId)!
.outcome
const withdrawReceiptId = successReceiptOutcome.receipt_ids[0]
const withdrawReceiptOutcome = nearBurnTx.receipts_outcome
.find(r => r.id === withdrawReceiptId)!
.outcome

// @ts-expect-error TODO
const burnReceiptId = withdrawReceiptOutcome.status.SuccessReceiptId

const txReceiptBlockHash = nearBurnTx.receipts_outcome
.find(r => r.id === burnReceiptId)!
// @ts-expect-error TODO
.block_hash

const receiptBlock = await nearProvider.block({ blockId: txReceiptBlockHash })
const receiptBlockHeight = Number(receiptBlock.header.height)
return { id: burnReceiptId, blockHeight: receiptBlockHeight }
const receiptBlock = await nearProvider.block({ blockId: bridgeReceipt.block_hash })
const blockHeight = Number(receiptBlock.header.height)
const blockTimestamp = Number(receiptBlock.header.timestamp)
return { id: bridgeReceipt.id, blockHeight, blockTimestamp, event }
}

export async function findAllTransactions (
Expand Down Expand Up @@ -349,45 +357,10 @@ export async function recover (
throw new Error(`Withdraw transaction failed: ${burnTxHash}`)
}

// Get withdraw event information from successValue
const nearBurnReceipt = await parseBurnReceipt(burnTx, nearProvider)
const burnReceiptOutcome = burnTx.receipts_outcome
.find(r => r.id === nearBurnReceipt.id)!
.outcome

// @ts-expect-error TODO
const successValue: string = burnReceiptOutcome.status.SuccessValue
if (!successValue) {
throw new Error(
`Invalid burnTx successValue: '${successValue}'
Full withdrawal transaction: ${JSON.stringify(burnTx)}`
)
}

// eslint-disable-next-line @typescript-eslint/no-extraneous-class
class BurnEvent {
constructor (args: any) {
Object.assign(this, args)
}
}
const SCHEMA = new Map([
[BurnEvent, {
kind: 'struct',
fields: [
['flag', 'u8'],
['amount', 'u128'],
['token', [20]],
['recipient', [20]]
]
}]
])
const burnEvent = deserializeBorsh(
SCHEMA, BurnEvent, Buffer.from(successValue, 'base64')
) as { amount: BN, token: Uint8Array, recipient: Uint8Array }
const nep141Factory = options.nep141Factory ?? getBridgeParams().nep141Factory
const nearBurnReceipt = await parseBurnReceipt(burnTx, nep141Factory, nearProvider)

const amount = burnEvent.amount.toString()
const recipient = '0x' + Buffer.from(burnEvent.recipient).toString('hex')
const erc20Address = '0x' + Buffer.from(burnEvent.token).toString('hex')
const { amount, recipient, token: erc20Address } = nearBurnReceipt.event
const symbol = options.symbol ?? await getSymbol({ erc20Address, options })
const decimals = options.decimals ?? await getDecimals({ erc20Address, options })
const sourceTokenName = 'a' + symbol
Expand Down Expand Up @@ -575,6 +548,7 @@ export async function checkBurn (
auroraRelayerAccount?: string
nearAccount?: Account
nearProvider?: najProviders.Provider
nep141Factory?: string
}
): Promise<Transfer> {
options = options ?? {}
Expand Down Expand Up @@ -671,8 +645,9 @@ export async function checkBurn (
}

let nearBurnReceipt
const nep141Factory = options.nep141Factory ?? getBridgeParams().nep141Factory
try {
nearBurnReceipt = await parseBurnReceipt(nearBurnTx, nearProvider)
nearBurnReceipt = await parseBurnReceipt(nearBurnTx, nep141Factory, nearProvider)
} catch (e) {
if (e instanceof TransferError) {
return {
Expand Down
100 changes: 39 additions & 61 deletions packages/aurora-ether/src/bridged-ether/sendToEthereum/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,31 +191,41 @@ export async function checkStatus (transfer: Transfer): Promise<Transfer> {
export async function parseBurnReceipt (
nearBurnTx: FinalExecutionOutcome,
nearProvider: najProviders.Provider
): Promise<{id: string, blockHeight: number }> {
const receiptIds = nearBurnTx.transaction_outcome.outcome.receipt_ids

if (receiptIds.length !== 1) {
throw new TransferError(
`Burn expects only one receipt, got ${receiptIds.length}.
Full withdrawal transaction: ${JSON.stringify(nearBurnTx)}`
)
): Promise<{id: string, blockHeight: number, blockTimestamp: number, event: { amount: string, recipient: string, etherCustodian: string }}> {
const bridgeReceipt: any = nearBurnTx.receipts_outcome[1]
if (!bridgeReceipt) {
throw new Error(`Failed to parse bridge receipt for ${JSON.stringify(nearBurnTx)}`)
}
const successValue = bridgeReceipt.outcome.status.SuccessValue
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
class BurnEvent {
constructor (args: any) {
Object.assign(this, args)
}
}
const SCHEMA = new Map([
[BurnEvent, {
kind: 'struct',
fields: [
['amount', 'u128'],
['recipient_id', [20]],
['eth_custodian_address', [20]]
]
}]
])
const rawEvent = deserializeBorsh(
SCHEMA, BurnEvent, Buffer.from(successValue, 'base64')
) as { amount: BN, recipient_id: Uint8Array, eth_custodian_address: Uint8Array}
const event = {
amount: rawEvent.amount.toString(),
recipient: '0x' + Buffer.from(rawEvent.recipient_id).toString('hex'),
etherCustodian: '0x' + Buffer.from(rawEvent.eth_custodian_address).toString('hex')
}

// Get receipt information for recording and building burn proof
const txReceiptId = receiptIds[0]
const successReceiptOutcome = nearBurnTx.receipts_outcome
.find(r => r.id === txReceiptId)!
.outcome
const burnReceiptId = successReceiptOutcome.receipt_ids[0]!

const txReceiptBlockHash = nearBurnTx.receipts_outcome
.find(r => r.id === burnReceiptId)!
// @ts-expect-error TODO
.block_hash

const receiptBlock = await nearProvider.block({ blockId: txReceiptBlockHash })
const receiptBlockHeight = Number(receiptBlock.header.height)
return { id: burnReceiptId, blockHeight: receiptBlockHeight }
const receiptBlock = await nearProvider.block({ blockId: bridgeReceipt.block_hash })
const blockHeight = Number(receiptBlock.header.height)
const blockTimestamp = Number(receiptBlock.header.timestamp)
return { id: bridgeReceipt.id, blockHeight, blockTimestamp, event }
}

export async function findAllTransactions (
Expand Down Expand Up @@ -318,57 +328,28 @@ export async function recover (
throw new Error(`Withdraw transaction failed: ${nearBurnTxHash}`)
}

// eslint-disable-next-line @typescript-eslint/no-extraneous-class
class BurnEvent {
constructor (args: any) {
Object.assign(this, args)
}
}
const SCHEMA = new Map([
[BurnEvent, {
kind: 'struct',
fields: [
['amount', 'u128'],
['recipient_id', [20]],
['eth_custodian_address', [20]]
]
}]
])
// @ts-expect-error TODO
const withdrawResult = burnTx.receipts_outcome[1].outcome.status.SuccessValue
const burnEvent = deserializeBorsh(
SCHEMA, BurnEvent, Buffer.from(withdrawResult, 'base64')
) as { amount: BN, recipient_id: Uint8Array, eth_custodian_address: Uint8Array}

const amount = burnEvent.amount.toString()
const recipient = '0x' + Buffer.from(burnEvent.recipient_id).toString('hex')
const etherCustodian = Buffer.from(burnEvent.eth_custodian_address).toString('hex')
const nearBurnReceipt = await parseBurnReceipt(burnTx, nearProvider)

const { amount, recipient, etherCustodian } = nearBurnReceipt.event
const etherCustodianAddress: string = options.etherCustodianAddress ?? bridgeParams.etherCustodianAddress
if (etherCustodian !== etherCustodianAddress.slice(2).toLowerCase()) {
if (etherCustodian.toLowerCase() !== etherCustodianAddress.toLowerCase()) {
throw new Error(
`Unexpected ether custodian: got${etherCustodian},
`Unexpected ether custodian: got ${etherCustodian},
expected ${etherCustodianAddress}`
)
}

const symbol = 'ETH'
const destinationTokenName = symbol
const decimals = 18
const sourceTokenName = 'a' + symbol
const sourceToken = symbol

const nearBurnReceipt = await parseBurnReceipt(burnTx, nearProvider)

// @ts-expect-error TODO
const txBlock = await nearProvider.block({ blockId: burnTx.transaction_outcome.block_hash })

// various attributes stored as arrays, to keep history of retries
const transfer = {
...transferDraft,

id: Math.random().toString().slice(2),
startTime: new Date(txBlock.header.timestamp / 10 ** 6).toISOString(),
startTime: new Date(nearBurnReceipt.blockTimestamp / 10 ** 6).toISOString(),
amount,
completedStep: BURN,
destinationTokenName,
Expand Down Expand Up @@ -640,14 +621,11 @@ export async function checkBurn (
throw e
}

// @ts-expect-error TODO
const txBlock = await nearProvider.block({ blockId: nearBurnTx.transaction_outcome.block_hash })

return {
...transfer,
status: status.IN_PROGRESS,
completedStep: BURN,
startTime: new Date(txBlock.header.timestamp / 10 ** 6).toISOString(),
startTime: new Date(nearBurnReceipt.blockTimestamp / 10 ** 6).toISOString(),
burnReceipts: [...transfer.burnReceipts, burnReceipt],
nearBurnHashes: [...transfer.nearBurnHashes, nearBurnHash],
nearBurnReceiptIds: [...transfer.nearBurnReceiptIds, nearBurnReceipt.id],
Expand Down
Loading

0 comments on commit 60af96c

Please sign in to comment.