Skip to content

Commit

Permalink
feat: ETH connector migration. (#98)
Browse files Browse the repository at this point in the history
* feat: ETH connector migration.

* refactor: Add selectEtherNep141Factory.

* fix: Withdraw ether using etherNep141Factory contract.

* fix: Remove is_used_proof check for old transfers.

* fix: Transfer Ether from NEAR to Aurora.

* fix: Migration block height check.

* fix: Update etherCustodianProxy.withdraw abi.

* fix: Error message check.

* check withdraw abi

* fix: Backward compatible abi check.

* fix: Remove duplicate findAllTransactions before migration.

* fix: Get outcome proof height from proof.

---------

Co-authored-by: Olga Kunyavskaya <olga.kunyavskaya@aurora.dev>
  • Loading branch information
paouvrard and olga24912 authored Aug 7, 2024
1 parent 400168e commit b401aee
Show file tree
Hide file tree
Showing 17 changed files with 290 additions and 152 deletions.
12 changes: 12 additions & 0 deletions .yarn/versions/55773dcb.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
releases:
"@near-eth/aurora-erc20": minor
"@near-eth/aurora-ether": major
"@near-eth/aurora-nep141": patch
"@near-eth/near-ether": major
"@near-eth/nep141-erc20": patch
"@near-eth/rainbow": major
"@near-eth/utils": patch
rainbow-bridge-client-monorepo: major

declined:
- "@near-eth/client"
78 changes: 41 additions & 37 deletions packages/aurora-erc20/src/bridged-erc20/sendToEthereum/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
findNearProof,
findFinalizationTxOnEthereum,
parseNep141BurnReceipt,
parseETHBurnReceipt
parseETHBurnReceipt,
selectEtherNep141Factory
} from '@near-eth/utils'
import { ethers } from 'ethers'
import bs58 from 'bs58'
Expand Down Expand Up @@ -42,8 +43,6 @@ const steps = [
UNLOCK
]

class TransferError extends Error {}

export interface TransferDraft extends TransferStatus {
type: string
finalityBlockHeights: number[]
Expand Down Expand Up @@ -89,6 +88,8 @@ export interface TransferOptions {
ethClientAbi?: string
nep141Factory?: string
auroraEvmAccount?: string
etherNep141Factory?: string
etherNep141FactoryMigrationHeight?: number
}

const transferDraft: TransferDraft = {
Expand Down Expand Up @@ -297,9 +298,10 @@ export async function recover (
}

const bridgeParams = getBridgeParams()
const nep141Factory = options.nep141Factory ?? bridgeParams.nep141Factory
const auroraEvmAccount = options.auroraEvmAccount ?? bridgeParams.auroraEvmAccount
let nearBurnReceipt, amount, recipient, symbol, decimals
if (options.symbol !== 'ETH') {
const nep141Factory = options.nep141Factory ?? bridgeParams.nep141Factory
nearBurnReceipt = await parseNep141BurnReceipt(burnTx, nep141Factory, nearProvider)
amount = nearBurnReceipt.event.amount
recipient = nearBurnReceipt.event.recipient
Expand All @@ -308,8 +310,15 @@ export async function recover (
decimals = options.decimals ?? await getDecimals({ erc20Address, options })
} else {
// Withdraw ERC-20 ETH from a silo which doesn't use ETH as native currency.
const auroraEvmAccount = options.auroraEvmAccount ?? bridgeParams.auroraEvmAccount
nearBurnReceipt = await parseETHBurnReceipt(burnTx, auroraEvmAccount, nearProvider)
const etherNep141Factory = await selectEtherNep141Factory({
etherNep141FactoryMigrationHeight: options.etherNep141FactoryMigrationHeight ?? bridgeParams.etherNep141FactoryMigrationHeight,
etherNep141Factory: options.etherNep141Factory ?? bridgeParams.etherNep141Factory,
auroraEvmAccount,
// @ts-expect-error
blockHash: burnTx.transaction_outcome.block_hash,
nearProvider
})
nearBurnReceipt = await parseETHBurnReceipt(burnTx, etherNep141Factory, nearProvider)
amount = nearBurnReceipt.event.amount
recipient = nearBurnReceipt.event.recipient
symbol = options.symbol
Expand All @@ -321,15 +330,12 @@ export async function recover (
const sourceTokenName = 'a' + symbol
const destinationTokenName = symbol

// @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 All @@ -339,7 +345,7 @@ export async function recover (
sourceToken,
symbol,
decimals,
auroraEvmAccount: options.auroraEvmAccount ?? bridgeParams.auroraEvmAccount,
auroraEvmAccount,
auroraChainId: options.auroraChainId ?? bridgeParams.auroraChainId,
burnHashes: [burnTxHash],
nearBurnHashes: [nearBurnTxHash],
Expand Down Expand Up @@ -502,6 +508,8 @@ export async function checkBurn (
nearAccount?: Account
nearProvider?: najProviders.Provider
nep141Factory?: string
etherNep141Factory?: string
etherNep141FactoryMigrationHeight?: number
}
): Promise<Transfer> {
options = options ?? {}
Expand Down Expand Up @@ -598,35 +606,27 @@ export async function checkBurn (
}

let nearBurnReceipt
const nep141Factory = options.nep141Factory ?? getBridgeParams().nep141Factory
try {
if (transfer.symbol !== 'ETH') {
nearBurnReceipt = await parseNep141BurnReceipt(nearBurnTx, nep141Factory, nearProvider)
} else {
// Withdraw ERC-20 ETH from a silo which doesn't use ETH as native currency.
nearBurnReceipt = await parseETHBurnReceipt(nearBurnTx, transfer.auroraEvmAccount ?? 'aurora', nearProvider)
}
} catch (e) {
if (e instanceof TransferError) {
return {
...transfer,
errors: [...transfer.errors, e.message],
status: status.FAILED
}
}
// Any other error like provider connection error should throw
// so that the transfer stays in progress and checkWithdraw will be called again.
throw e
if (transfer.symbol !== 'ETH') {
const nep141Factory = options.nep141Factory ?? bridgeParams.nep141Factory
nearBurnReceipt = await parseNep141BurnReceipt(nearBurnTx, nep141Factory, nearProvider)
} else {
// Withdraw ERC-20 ETH from a silo which doesn't use ETH as native currency.
const etherNep141Factory = await selectEtherNep141Factory({
etherNep141FactoryMigrationHeight: options.etherNep141FactoryMigrationHeight ?? bridgeParams.etherNep141FactoryMigrationHeight,
etherNep141Factory: options.etherNep141Factory ?? bridgeParams.etherNep141Factory,
auroraEvmAccount: bridgeParams.auroraEvmAccount,
// @ts-expect-error
blockHash: nearBurnTx.transaction_outcome.block_hash,
nearProvider
})
nearBurnReceipt = await parseETHBurnReceipt(nearBurnTx, etherNep141Factory, nearProvider)
}

// @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 Expand Up @@ -716,12 +716,15 @@ export async function checkSync (
options.nearAccount?.connection.provider ??
getNearProvider()
if (nearOnEthClientBlockHeight > burnBlockHeight) {
const etherNep141FactoryMigrationHeight = options.etherNep141FactoryMigrationHeight ?? bridgeParams.etherNep141FactoryMigrationHeight
const etherNep141Factory = burnBlockHeight > etherNep141FactoryMigrationHeight
? (options.etherNep141Factory ?? bridgeParams.etherNep141Factory)
: bridgeParams.auroraEvmAccount
proof = await findNearProof(
last(transfer.nearBurnReceiptIds),
// NOTE: If ETH is being transfered with @near-eth/aurora-erc20,
// it means that ETH is not the silo's native currency
// NOTE: If ETH is being transfered with @near-eth/aurora-erc20, it means that ETH is not the silo's native currency
transfer.symbol === 'ETH'
? bridgeParams.auroraEvmAccount
? etherNep141Factory
: (options.nep141Factory ?? bridgeParams.nep141Factory),
nearOnEthClientBlockHeight,
nearProvider,
Expand Down Expand Up @@ -835,6 +838,7 @@ export async function unlock (
// in case there was a reorg.
const safeReorgHeight = await provider.getBlockNumber() - 20
const pendingUnlockTx = await ethTokenLocker.unlockToken(borshProof, transfer.nearOnEthClientBlockHeight)
// TODO: Handle etherCustodian.withdraw when ETH is not the native silo token.

return {
...transfer,
Expand Down
86 changes: 55 additions & 31 deletions packages/aurora-ether/src/bridged-ether/sendToEthereum/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
findNearProof,
findFinalizationTxOnEthereum,
parseETHBurnReceipt,
parseNep141BurnReceipt
parseNep141BurnReceipt,
selectEtherNep141Factory
} from '@near-eth/utils'
import { ethers } from 'ethers'
import bs58 from 'bs58'
Expand Down Expand Up @@ -40,8 +41,6 @@ const steps = [
UNLOCK
]

class TransferError extends Error {}

export interface TransferDraft extends TransferStatus {
type: string
finalityBlockHeights: number[]
Expand All @@ -67,7 +66,7 @@ export interface Transfer extends TransferDraft, TransactionInfo {
symbol: string
checkSyncInterval?: number
nextCheckSyncTimestamp?: Date
proof?: Uint8Array
proof?: any
auroraEvmAccount?: string
auroraChainId?: string
}
Expand All @@ -88,6 +87,8 @@ export interface TransferOptions {
auroraProvider?: ethers.providers.JsonRpcProvider
auroraEvmAccount?: string
symbol?: string
etherNep141Factory?: string
etherNep141FactoryMigrationHeight?: number
}

const transferDraft: TransferDraft = {
Expand Down Expand Up @@ -286,12 +287,20 @@ export async function recover (
let recipient
if (options.symbol && options.symbol !== 'ETH') {
// Withdraw native currency from a silo which doesn't use ETH as native currency
const nep141Factory = options.nep141Factory ?? getBridgeParams().nep141Factory
const nep141Factory = options.nep141Factory ?? bridgeParams.nep141Factory
nearBurnReceipt = await parseNep141BurnReceipt(burnTx, nep141Factory, nearProvider)
amount = nearBurnReceipt.event.amount
recipient = nearBurnReceipt.event.recipient
} else {
nearBurnReceipt = await parseETHBurnReceipt(burnTx, auroraEvmAccount, nearProvider)
const etherNep141Factory = await selectEtherNep141Factory({
etherNep141FactoryMigrationHeight: options.etherNep141FactoryMigrationHeight ?? bridgeParams.etherNep141FactoryMigrationHeight,
etherNep141Factory: options.etherNep141Factory ?? bridgeParams.etherNep141Factory,
auroraEvmAccount,
// @ts-expect-error
blockHash: burnTx.transaction_outcome.block_hash,
nearProvider
})
nearBurnReceipt = await parseETHBurnReceipt(burnTx, etherNep141Factory, nearProvider)
amount = nearBurnReceipt.event.amount
recipient = nearBurnReceipt.event.recipient
}
Expand Down Expand Up @@ -485,6 +494,8 @@ export async function checkBurn (
nearAccount?: Account
nearProvider?: najProviders.Provider
nep141Factory?: string
etherNep141Factory?: string
etherNep141FactoryMigrationHeight?: number
}
): Promise<Transfer> {
options = options ?? {}
Expand Down Expand Up @@ -580,25 +591,20 @@ export async function checkBurn (
}

let nearBurnReceipt
try {
if (transfer.symbol !== 'ETH') {
// Withdraw native currency from a silo which doesn't use ETH as native currency
const nep141Factory = options.nep141Factory ?? getBridgeParams().nep141Factory
nearBurnReceipt = await parseNep141BurnReceipt(nearBurnTx, nep141Factory, nearProvider)
} else {
nearBurnReceipt = await parseETHBurnReceipt(nearBurnTx, transfer.auroraEvmAccount ?? 'aurora', nearProvider)
}
} catch (e) {
if (e instanceof TransferError) {
return {
...transfer,
errors: [...transfer.errors, e.message],
status: status.FAILED
}
}
// Any other error like provider connection error should throw
// so that the transfer stays in progress and checkWithdraw will be called again.
throw e
if (transfer.symbol !== 'ETH') {
// Withdraw native currency from a silo which doesn't use ETH as native currency
const nep141Factory = options.nep141Factory ?? bridgeParams.nep141Factory
nearBurnReceipt = await parseNep141BurnReceipt(nearBurnTx, nep141Factory, nearProvider)
} else {
const etherNep141Factory = await selectEtherNep141Factory({
etherNep141FactoryMigrationHeight: options.etherNep141FactoryMigrationHeight ?? bridgeParams.etherNep141FactoryMigrationHeight,
etherNep141Factory: options.etherNep141Factory ?? bridgeParams.etherNep141Factory,
auroraEvmAccount: bridgeParams.auroraEvmAccount,
// @ts-expect-error
blockHash: nearBurnTx.transaction_outcome.block_hash,
nearProvider
})
nearBurnReceipt = await parseETHBurnReceipt(nearBurnTx, etherNep141Factory, nearProvider)
}

return {
Expand Down Expand Up @@ -693,14 +699,16 @@ export async function checkSync (
options.nearAccount?.connection.provider ??
getNearProvider()
if (nearOnEthClientBlockHeight > burnBlockHeight) {
const etherNep141FactoryMigrationHeight = options.etherNep141FactoryMigrationHeight ?? bridgeParams.etherNep141FactoryMigrationHeight
const etherNep141Factory = burnBlockHeight > etherNep141FactoryMigrationHeight
? (options.etherNep141Factory ?? bridgeParams.etherNep141Factory)
: bridgeParams.auroraEvmAccount
proof = await findNearProof(
last(transfer.nearBurnReceiptIds),
// NOTE: options.auroraEvmAccount cannot be used because checkSync can be called by recover using a different silo's auroraEvmAccount.
// NOTE: If another token than ETH is being transfered with @near-eth/aurora-ether,
// it means that ETH is not the silo's native currency
// NOTE: If another token than ETH is being transfered with @near-eth/aurora-ether it means that ETH is not the silo's native currency
transfer.symbol !== 'ETH'
? bridgeParams.nep141Factory
: bridgeParams.auroraEvmAccount,
: etherNep141Factory,
nearOnEthClientBlockHeight,
nearProvider,
provider,
Expand Down Expand Up @@ -804,15 +812,31 @@ export async function unlock (
// Unlock
const borshProof = borshifyOutcomeProof(proof)

const etherCustodianProxyAddress = options.etherCustodianProxyAddress ?? bridgeParams.etherCustodianProxyAddress
const etherCustodianAddress = options.etherCustodianAddress ?? bridgeParams.etherCustodianAddress
const ethTokenLocker = new ethers.Contract(
options.etherCustodianProxyAddress ?? bridgeParams.etherCustodianProxyAddress,
etherCustodianProxyAddress,
options.etherCustodianProxyAbi ?? bridgeParams.etherCustodianProxyAbi,
options.signer ?? provider.getSigner()
)
// If this tx is dropped and replaced, lower the search boundary
// in case there was a reorg.
const safeReorgHeight = await provider.getBlockNumber() - 20
const pendingUnlockTx = await ethTokenLocker.withdraw(borshProof, transfer.nearOnEthClientBlockHeight)

let pendingUnlockTx
if (etherCustodianProxyAddress.toLowerCase() !== etherCustodianAddress.toLowerCase()) {
pendingUnlockTx = await ethTokenLocker.withdraw(
borshProof,
transfer.nearOnEthClientBlockHeight,
proof.block_header_lite.inner_lite.height
)
} else {
pendingUnlockTx = await ethTokenLocker.withdraw(
borshProof,
transfer.nearOnEthClientBlockHeight
)
}
// TODO: Handle erc20Locker.unlockToken when ETH is not the native silo token.

return {
...transfer,
Expand Down
Loading

0 comments on commit b401aee

Please sign in to comment.