Skip to content

Commit

Permalink
Cross-chain deposit flow
Browse files Browse the repository at this point in the history
Here we expose a possibility to initiate cross-chain deposit that targets an
L2 chain other than the L1 chain the tBTC system is deployed on.
This is achieved by exposing the `initiateCrossChainDeposit` method
on the `DepositsService` component.
  • Loading branch information
lukasz-zimnoch committed Mar 14, 2024
1 parent cf334f9 commit 119335b
Show file tree
Hide file tree
Showing 8 changed files with 485 additions and 34 deletions.
14 changes: 10 additions & 4 deletions typescript/src/lib/base/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { chainIdFromSigner, EthereumSigner } from "../ethereum"
import {
chainIdFromSigner,
ethereumAddressFromSigner,
EthereumSigner,
} from "../ethereum"
import { BaseL2BitcoinDepositor } from "./l2-bitcoin-depositor"
import { BaseL2TBTCToken } from "./l2-tbtc-token"
import { Chains, L2CrossChainContracts } from "../contracts"
Expand All @@ -9,11 +13,11 @@ export * from "./l2-tbtc-token"
/**
* Loads Base implementation of tBTC cross-chain contracts for the given Base
* chain ID and attaches the given signer there.
* @param signer Signer that should be attached to tBTC contracts.
* @param signer Signer that should be attached to the contracts.
* @param chainId Base chain ID.
* @returns Handle to tBTC cross-chain contracts.
* @returns Handle to the contracts.
* @throws Throws an error if the signer's Base chain ID is other than
* the one used to load tBTC contracts.
* the one used to load contracts.
*/
export async function loadBaseCrossChainContracts(
signer: EthereumSigner,
Expand All @@ -30,6 +34,8 @@ export async function loadBaseCrossChainContracts(
{ signerOrProvider: signer },
chainId
)
l2BitcoinDepositor.setDepositOwner(await ethereumAddressFromSigner(signer))

const l2TbtcToken = new BaseL2TBTCToken({ signerOrProvider: signer }, chainId)

return {
Expand Down
79 changes: 77 additions & 2 deletions typescript/src/lib/base/l2-bitcoin-depositor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,24 @@ import {
EthersContractHandle,
} from "../ethereum/adapter"
import { L2BitcoinDepositor as L2BitcoinDepositorTypechain } from "../../../typechain/L2BitcoinDepositor"
import { ChainIdentifier, Chains, L2BitcoinDepositor } from "../contracts"
import { EthereumAddress } from "../ethereum"
import {
ChainIdentifier,
Chains,
CrossChainExtraDataEncoder,
DepositReceipt,
L2BitcoinDepositor,
} from "../contracts"
import {
EthereumAddress,
EthereumCrossChainExtraDataEncoder,
packRevealDepositParameters,
} from "../ethereum"

// TODO: Uncomment once BaseL2BitcoinDepositor is available on Base mainnet.
// import BaseL2BitcoinDepositorDeployment from "./artifacts/base/BaseL2BitcoinDepositor.json"
import BaseSepoliaL2BitcoinDepositorDeployment from "./artifacts/baseSepolia/BaseL2BitcoinDepositor.json"
import { BitcoinRawTxVectors } from "../bitcoin"
import { Hex } from "../utils"

/**
* Implementation of the Base L2BitcoinDepositor handle.
Expand All @@ -19,6 +31,9 @@ export class BaseL2BitcoinDepositor
extends EthersContractHandle<L2BitcoinDepositorTypechain>
implements L2BitcoinDepositor
{
readonly #extraDataEncoder: CrossChainExtraDataEncoder
#depositOwner: ChainIdentifier | undefined

constructor(config: EthersContractConfig, chainId: Chains.Base) {
let deployment: EthersContractDeployment

Expand All @@ -35,6 +50,8 @@ export class BaseL2BitcoinDepositor
}

super(config, deployment)

this.#extraDataEncoder = new EthereumCrossChainExtraDataEncoder()
}

// eslint-disable-next-line valid-jsdoc
Expand All @@ -44,4 +61,62 @@ export class BaseL2BitcoinDepositor
getChainIdentifier(): ChainIdentifier {
return EthereumAddress.from(this._instance.address)
}

// eslint-disable-next-line valid-jsdoc
/**
* @see {L2BitcoinDepositor#getDepositOwner}
*/
getDepositOwner(): ChainIdentifier | undefined {
return this.#depositOwner
}

// eslint-disable-next-line valid-jsdoc
/**
* @see {L2BitcoinDepositor#setDepositOwner}
*/
setDepositOwner(depositOwner: ChainIdentifier | undefined) {
this.#depositOwner = depositOwner
}

// eslint-disable-next-line valid-jsdoc
/**
* @see {L2BitcoinDepositor#extraDataEncoder}
*/
extraDataEncoder(): CrossChainExtraDataEncoder {
return this.#extraDataEncoder
}

// eslint-disable-next-line valid-jsdoc
/**
* @see {L2BitcoinDepositor#initializeDeposit}
*/
async initializeDeposit(
depositTx: BitcoinRawTxVectors,
depositOutputIndex: number,
deposit: DepositReceipt,
vault?: ChainIdentifier
): Promise<Hex> {
const { fundingTx, reveal } = packRevealDepositParameters(
depositTx,
depositOutputIndex,
deposit,
vault
)

if (!deposit.extraData) {
throw new Error("Extra data is required")
}

const l2DepositOwner = this.extraDataEncoder().decodeDepositOwner(
deposit.extraData
)

const tx = await this._instance.initializeDeposit(
fundingTx,
reveal,
`0x${l2DepositOwner.identifierHex}`
)

return Hex.from(tx.hash)
}
}
6 changes: 3 additions & 3 deletions typescript/src/lib/contracts/chain.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-unused-vars */
/**
* Chains supported by tBTC v2 SDK.
* Chains supported by tBTC v2 contracts.
*/
export namespace Chains {
export enum Ethereum {
Expand All @@ -16,7 +16,7 @@ export namespace Chains {
}

/**
* Layer 2 chains supported by tBTC v2 SDK.
* Layer 2 chains supported by tBTC v2 contracts.
*/
export type L2Chain = Exclude<keyof typeof Chains, "Ethereum">

Expand All @@ -35,7 +35,7 @@ export type ChainMapping = {
}

/**
* List of chain mappings supported by tBTC v2 SDK.
* List of chain mappings supported by tBTC v2 contracts.
*/
export const ChainMappings: ChainMapping[] = [
{
Expand Down
85 changes: 85 additions & 0 deletions typescript/src/lib/contracts/cross-chain.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { ChainIdentifier } from "./chain-identifier"
import { BigNumber } from "ethers"
import { ChainMapping, L2Chain } from "./chain"
import { BitcoinRawTxVectors } from "../bitcoin"
import { DepositReceipt } from "./bridge"
import { Hex } from "../utils"

/**
* Convenience type aggregating TBTC cross-chain contracts forming a connector
Expand Down Expand Up @@ -67,6 +70,43 @@ export interface L2BitcoinDepositor {
* Gets the chain-specific identifier of this contract.
*/
getChainIdentifier(): ChainIdentifier

/**
* Gets the identifier that should be used as the owner of the deposits
* issued by this contract.
* @returns The identifier of the deposit owner or undefined if not set.
*/
getDepositOwner(): ChainIdentifier | undefined

/**
* Sets the identifier that should be used as the owner of the deposits
* issued by this contract.
* @param depositOwner Identifier of the deposit owner or undefined to clear.
*/
setDepositOwner(depositOwner: ChainIdentifier): void

/**
* @returns Extra data encoder for this contract. The encoder is used to
* encode and decode the extra data included in the cross-chain deposit script.
*/
extraDataEncoder(): CrossChainExtraDataEncoder

/**
* Initializes the cross-chain deposit indirectly through the given L2 chain.
* @param depositTx Deposit transaction data
* @param depositOutputIndex Index of the deposit transaction output that
* funds the revealed deposit
* @param deposit Data of the revealed deposit
* @param vault Optional parameter denoting the vault the given deposit
* should be routed to
* @returns Transaction hash of the reveal deposit transaction.
*/
initializeDeposit(
depositTx: BitcoinRawTxVectors,
depositOutputIndex: number,
deposit: DepositReceipt,
vault?: ChainIdentifier
): Promise<Hex>
}

/**
Expand All @@ -78,4 +118,49 @@ export interface L1BitcoinDepositor {
* Gets the chain-specific identifier of this contract.
*/
getChainIdentifier(): ChainIdentifier

/**
* @returns Extra data encoder for this contract. The encoder is used to
* encode and decode the extra data included in the cross-chain deposit script.
*/
extraDataEncoder(): CrossChainExtraDataEncoder

/**
* Initializes the cross-chain deposit directly on the given L1 chain.
* @param depositTx Deposit transaction data
* @param depositOutputIndex Index of the deposit transaction output that
* funds the revealed deposit
* @param deposit Data of the revealed deposit
* @param vault Optional parameter denoting the vault the given deposit
* should be routed to
* @returns Transaction hash of the reveal deposit transaction.
*/
initializeDeposit(
depositTx: BitcoinRawTxVectors,
depositOutputIndex: number,
deposit: DepositReceipt,
vault?: ChainIdentifier
): Promise<Hex>
}

/**
* Interface for encoding and decoding the extra data included in the
* cross-chain deposit script.
*/
export interface CrossChainExtraDataEncoder {
/**
* Encodes the given deposit owner identifier into the extra data.
* @param depositOwner Identifier of the deposit owner to encode.
* For cross-chain deposits, the deposit owner is typically an
* identifier on the L2 chain.
* @returns Encoded extra data.
*/
encodeDepositOwner(depositOwner: ChainIdentifier): Hex

/**
* Decodes the extra data into the deposit owner identifier.
* @param extraData Extra data to decode.
* @returns Identifier of the deposit owner.
*/
decodeDepositOwner(extraData: Hex): ChainIdentifier
}
86 changes: 85 additions & 1 deletion typescript/src/lib/ethereum/l1-bitcoin-depositor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ import { L1BitcoinDepositor as L1BitcoinDepositorTypechain } from "../../../type
import {
ChainIdentifier,
Chains,
CrossChainExtraDataEncoder,
DepositReceipt,
L1BitcoinDepositor,
L2Chain,
} from "../contracts"
import { EthereumAddress } from "./index"
import { EthereumAddress, packRevealDepositParameters } from "./index"
import { BitcoinRawTxVectors } from "../bitcoin"
import { Hex } from "../utils"

// TODO: Uncomment once BaseL1BitcoinDepositor is available on Ethereum mainnet.
// import MainnetBaseL1BitcoinDepositorDeployment from "./artifacts/mainnet/BaseL1BitcoinDepositor.json"
Expand Down Expand Up @@ -46,6 +50,8 @@ export class EthereumL1BitcoinDepositor
extends EthersContractHandle<L1BitcoinDepositorTypechain>
implements L1BitcoinDepositor
{
readonly #extraDataEncoder: CrossChainExtraDataEncoder

constructor(
config: EthersContractConfig,
chainId: Chains.Ethereum,
Expand All @@ -65,6 +71,8 @@ export class EthereumL1BitcoinDepositor
}

super(config, deployment)

this.#extraDataEncoder = new EthereumCrossChainExtraDataEncoder()
}

// eslint-disable-next-line valid-jsdoc
Expand All @@ -74,4 +82,80 @@ export class EthereumL1BitcoinDepositor
getChainIdentifier(): ChainIdentifier {
return EthereumAddress.from(this._instance.address)
}

// eslint-disable-next-line valid-jsdoc
/**
* @see {L1BitcoinDepositor#extraDataEncoder}
*/
extraDataEncoder(): CrossChainExtraDataEncoder {
return this.#extraDataEncoder
}

// eslint-disable-next-line valid-jsdoc
/**
* @see {L1BitcoinDepositor#initializeDeposit}
*/
async initializeDeposit(
depositTx: BitcoinRawTxVectors,
depositOutputIndex: number,
deposit: DepositReceipt,
vault?: ChainIdentifier
): Promise<Hex> {
const { fundingTx, reveal } = packRevealDepositParameters(
depositTx,
depositOutputIndex,
deposit,
vault
)

if (!deposit.extraData) {
throw new Error("Extra data is required")
}

const l2DepositOwner = this.extraDataEncoder().decodeDepositOwner(
deposit.extraData
)

const tx = await this._instance.initializeDeposit(
fundingTx,
reveal,
`0x${l2DepositOwner.identifierHex}`
)

return Hex.from(tx.hash)
}
}

/**
* Implementation of the Ethereum CrossChainExtraDataEncoder.
* @see {CrossChainExtraDataEncoder} for reference.
*/
export class EthereumCrossChainExtraDataEncoder
implements CrossChainExtraDataEncoder
{
// eslint-disable-next-line valid-jsdoc
/**
* @see {CrossChainExtraDataEncoder#encodeDepositOwner}
*/
encodeDepositOwner(depositOwner: ChainIdentifier): Hex {
// Make sure we are dealing with an Ethereum address. If not, this
// call will throw.
const address = EthereumAddress.from(depositOwner.identifierHex)

// Extra data must be 32-byte so prefix the 20-byte address with
// 12 zero bytes.
return Hex.from(`000000000000000000000000${address.identifierHex}`)
}

// eslint-disable-next-line valid-jsdoc
/**
* @see {CrossChainExtraDataEncoder#decodeDepositOwner}
*/
decodeDepositOwner(extraData: Hex): ChainIdentifier {
// Cut the first 12 zero bytes of the extra data and convert the rest to

return EthereumAddress.from(
Hex.from(extraData.toBuffer().subarray(12)).toString()
)
}
}
Loading

0 comments on commit 119335b

Please sign in to comment.