Skip to content

Commit

Permalink
feat: Aurora Cloud silo support. (#93)
Browse files Browse the repository at this point in the history
* feat: Aurora Cloud silo support.

* chore: Yarn version check.

* feat: Silo with different native currency support.

* chore: Yarn version check.

* feat: aurora-nep141 bridged-ether silo support.

* feat: Record auroraChainId is transfer.

* feat: Unwrap wNEAR option.

* fix: Replace last getAuroraProvider with getAuroraCloudProvider.

* fix: More robust bridge proof receipt parsing.
  • Loading branch information
paouvrard authored Feb 28, 2024
1 parent 9009eca commit c298534
Show file tree
Hide file tree
Showing 20 changed files with 501 additions and 340 deletions.
10 changes: 10 additions & 0 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
10 changes: 10 additions & 0 deletions .yarn/versions/5012e96b.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
releases:
"@near-eth/aurora-erc20": minor
"@near-eth/aurora-ether": minor
"@near-eth/aurora-nep141": minor
"@near-eth/client": minor
"@near-eth/near-ether": patch
"@near-eth/nep141-erc20": patch
"@near-eth/rainbow": minor
"@near-eth/utils": minor
rainbow-bridge-client-monorepo: minor
125 changes: 53 additions & 72 deletions packages/aurora-erc20/src/bridged-erc20/sendToEthereum/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,20 @@ import {
borshifyOutcomeProof,
nearOnEthSyncHeight,
findNearProof,
findFinalizationTxOnEthereum
findFinalizationTxOnEthereum,
parseNep141BurnReceipt,
parseETHBurnReceipt
} from '@near-eth/utils'
import { ethers } from 'ethers'
import bs58 from 'bs58'
import BN from 'bn.js'
import {
deserialize as deserializeBorsh
} from 'near-api-js/lib/utils/serialize'
import { FinalExecutionOutcome } from 'near-api-js/lib/providers'
import { Account, providers as najProviders } from 'near-api-js'
import { track } from '@near-eth/client'
import { stepsFor } from '@near-eth/client/dist/i18nHelpers'
import * as status from '@near-eth/client/dist/statuses'
import { TransferStatus, TransactionInfo } from '@near-eth/client/dist/types'
import {
getSignerProvider,
getAuroraProvider,
getAuroraCloudProvider,
getEthProvider,
getNearProvider,
formatLargeNum,
Expand Down Expand Up @@ -73,6 +70,8 @@ export interface Transfer extends TransferDraft, TransactionInfo {
checkSyncInterval?: number
nextCheckSyncTimestamp?: Date
proof?: Uint8Array
auroraEvmAccount?: string
auroraChainId?: string
}

export interface TransferOptions {
Expand All @@ -83,11 +82,13 @@ export interface TransferOptions {
erc20Abi?: string
sendToEthereumSyncInterval?: number
ethChainId?: number
auroraChainId?: string
nearAccount?: Account
nearProvider?: najProviders.Provider
ethClientAddress?: string
ethClientAbi?: string
nep141Factory?: string
auroraEvmAccount?: string
}

const transferDraft: TransferDraft = {
Expand Down Expand Up @@ -192,56 +193,6 @@ 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, 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')
}

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 (
{ fromBlock, toBlock, sender, erc20Address, options }: {
fromBlock: number | string
Expand All @@ -258,7 +209,7 @@ export async function findAllTransactions (
): Promise<string[]> {
options = options ?? {}
const bridgeParams = getBridgeParams()
const provider = options.provider ?? getAuroraProvider()
const provider = options.provider ?? getAuroraCloudProvider({ auroraEvmAccount: options?.auroraEvmAccount })
const auroraErc20Address = options.auroraErc20Address ?? await getAuroraErc20Address(
{ erc20Address, options }
)
Expand Down Expand Up @@ -318,7 +269,7 @@ export async function recover (
options.nearAccount?.connection.provider ??
getNearProvider()

const auroraProvider = options.auroraProvider ?? getAuroraProvider()
const auroraProvider = options.auroraProvider ?? getAuroraCloudProvider({ auroraEvmAccount: options?.auroraEvmAccount })
// Ethers formats the receipts and removes nearTransactionHash
const auroraBurnReceipt = await auroraProvider.send('eth_getTransactionReceipt', [burnTxHash])
const decodedTxHash = Buffer.from(auroraBurnReceipt.nearTransactionHash.slice(2), 'hex')
Expand All @@ -345,12 +296,28 @@ export async function recover (
throw new Error(`Withdraw transaction failed: ${burnTxHash}`)
}

const nep141Factory = options.nep141Factory ?? getBridgeParams().nep141Factory
const nearBurnReceipt = await parseBurnReceipt(burnTx, nep141Factory, nearProvider)

const { amount, recipient, token: erc20Address } = nearBurnReceipt.event
const symbol = options.symbol ?? await getSymbol({ erc20Address, options })
const decimals = options.decimals ?? await getDecimals({ erc20Address, options })
const bridgeParams = getBridgeParams()
const nep141Factory = options.nep141Factory ?? bridgeParams.nep141Factory
let nearBurnReceipt, amount, recipient, symbol, decimals
if (options.symbol !== 'ETH') {
nearBurnReceipt = await parseNep141BurnReceipt(burnTx, nep141Factory, nearProvider)
amount = nearBurnReceipt.event.amount
recipient = nearBurnReceipt.event.recipient
const { token: erc20Address } = nearBurnReceipt.event
symbol = options.symbol ?? await getSymbol({ erc20Address, options })
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)
amount = nearBurnReceipt.event.amount
recipient = nearBurnReceipt.event.recipient
symbol = options.symbol
decimals = options.decimals
if (!symbol || !decimals || !auroraEvmAccount) {
throw new Error('Must provide symbol and decimals options to reciver ETH transfer from silo with different native currency')
}
}
const sourceTokenName = 'a' + symbol
const destinationTokenName = symbol

Expand All @@ -372,6 +339,8 @@ export async function recover (
sourceToken,
symbol,
decimals,
auroraEvmAccount: options.auroraEvmAccount ?? bridgeParams.auroraEvmAccount,
auroraChainId: options.auroraChainId ?? bridgeParams.auroraChainId,
burnHashes: [burnTxHash],
nearBurnHashes: [nearBurnTxHash],
nearBurnReceiptIds: [nearBurnReceipt.id],
Expand All @@ -395,9 +364,9 @@ export async function recover (
* @param params.options.auroraChainId Aurora chain id of the bridge.
* @param params.options.auroraErc20Abi Aurora ERC-20 abi to call withdrawToEthereum.
* @param params.options.auroraErc20Address params.erc20Address's address on Aurora.
* @param options.auroraEvmAccount Aurora account on NEAR.
* @param params.options.auroraEvmAccount Aurora Cloud silo account on NEAR.
* @param params.options.nep141Factory ERC-20 connector factory to determine the NEAR address.
* @param options.provider Aurora provider to use.
* @param params.options.provider Aurora provider to use.
* @param params.options.nearAccount Connected NEAR wallet account to use.
* @param params.options.nearProvider NEAR provider.
* @param params.options.signer Ethers signer to use.
Expand Down Expand Up @@ -440,9 +409,10 @@ export async function initiate (

const signer = options.signer ?? provider.getSigner()
const sender = options.sender ?? (await signer.getAddress()).toLowerCase()
const bridgeParams = getBridgeParams()

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

id: Math.random().toString().slice(2),
Expand All @@ -453,6 +423,8 @@ export async function initiate (
sender,
sourceToken: auroraErc20Address,
sourceTokenName,
auroraEvmAccount: options.auroraEvmAccount ?? bridgeParams.auroraEvmAccount,
auroraChainId: options.auroraChainId ?? bridgeParams.auroraChainId,
symbol,
decimals
}
Expand Down Expand Up @@ -534,12 +506,12 @@ export async function checkBurn (
): Promise<Transfer> {
options = options ?? {}
const bridgeParams = getBridgeParams()
const provider = options.provider ?? getAuroraProvider()
const provider = options.provider ?? getAuroraCloudProvider({ auroraEvmAccount: transfer.auroraEvmAccount })

const burnHash = last(transfer.burnHashes)

const ethChainId: number = (await provider.getNetwork()).chainId
const expectedChainId: number = options.auroraChainId ?? bridgeParams.auroraChainId
const expectedChainId: number = options.auroraChainId ?? transfer.auroraChainId ?? bridgeParams.auroraChainId
if (ethChainId !== expectedChainId) {
throw new Error(
`Wrong aurora network for checkLock, expected: ${expectedChainId}, got: ${ethChainId}`
Expand Down Expand Up @@ -628,7 +600,12 @@ export async function checkBurn (
let nearBurnReceipt
const nep141Factory = options.nep141Factory ?? getBridgeParams().nep141Factory
try {
nearBurnReceipt = await parseBurnReceipt(nearBurnTx, nep141Factory, nearProvider)
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 {
Expand Down Expand Up @@ -741,7 +718,11 @@ export async function checkSync (
if (nearOnEthClientBlockHeight > burnBlockHeight) {
proof = await findNearProof(
last(transfer.nearBurnReceiptIds),
options.nep141Factory ?? bridgeParams.nep141Factory,
// 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
: (options.nep141Factory ?? bridgeParams.nep141Factory),
nearOnEthClientBlockHeight,
nearProvider,
provider,
Expand Down
7 changes: 5 additions & 2 deletions packages/aurora-erc20/src/natural-erc20/sendToAurora/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface Transfer extends TransferDraft, TransactionInfo {
checkSyncInterval?: number
nextCheckSyncTimestamp?: Date
proof?: Uint8Array
auroraEvmAccount?: string
}

export interface TransferOptions {
Expand Down Expand Up @@ -272,6 +273,7 @@ export async function recover (
sourceTokenName,
symbol,
decimals,
auroraEvmAccount: options.auroraEvmAccount ?? bridgeParams.auroraEvmAccount,
status: status.IN_PROGRESS,
lockHashes: [lockTxHash],
lockReceipts: [receipt]
Expand Down Expand Up @@ -299,7 +301,7 @@ export async function recover (
* @param params.options.erc20LockerAddress Rainbow bridge ERC-20 token locker address.
* @param params.options.erc20LockerAbi Rainbow bridge ERC-20 token locker abi.
* @param params.options.erc20Abi Standard ERC-20 token abi.
* @param options.auroraEvmAccount Aurora account on NEAR.
* @param params.options.auroraEvmAccount Aurora Cloud silo account on NEAR.
* @param params.options.signer Ethers signer to use.
* @returns The created transfer object.
*/
Expand Down Expand Up @@ -333,7 +335,7 @@ export async function initiate (
const sender = options.sender ?? (await signer.getAddress()).toLowerCase()

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

id: Math.random().toString().slice(2),
Expand All @@ -344,6 +346,7 @@ export async function initiate (
sender,
sourceToken: erc20Address,
sourceTokenName,
auroraEvmAccount: options.auroraEvmAccount ?? getBridgeParams().auroraEvmAccount,
symbol,
decimals
}
Expand Down
Loading

0 comments on commit c298534

Please sign in to comment.