Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for deposits with extra data #749

Closed
8 of 10 tasks
lukasz-zimnoch opened this issue Nov 16, 2023 · 1 comment
Closed
8 of 10 tasks

Support for deposits with extra data #749

lukasz-zimnoch opened this issue Nov 16, 2023 · 1 comment
Assignees
Labels
⛓️ solidity Solidity contracts 🔌 typescript TypeScript library

Comments

@lukasz-zimnoch
Copy link
Member

lukasz-zimnoch commented Nov 16, 2023

This task is about implementing a feature allowing to reveal deposits with extra data, as specified in RFC 11.

This feature opens multiple possibilities. Notably, it allows creating intermediary smart contracts that can reveal deposits on behalf of the depositor and provide additional services regarding minted TBTC (e.g., deposit it to a yield protocol or bridge it to an L2 chain). This, in turn, empowers third-party protocols to use tBTC as a foundation and propose additional value on top of it.

The scope of this task includes:

  • Implementation of the feature in the Bridge
  • Implementation of the support in the client so those deposits can be swept
  • Implementation of the support in the SDK

Development work

  1. ⛓️ solidity
    lukasz-zimnoch
  2. 🔌 typescript
    lukasz-zimnoch
  3. 🔌 typescript
    lukasz-zimnoch
  4. 📟 client
    lukasz-zimnoch
  5. 📟 client
    lukasz-zimnoch

Deployment checklist

@lukasz-zimnoch lukasz-zimnoch self-assigned this Nov 16, 2023
@lukasz-zimnoch lukasz-zimnoch added ⛓️ solidity Solidity contracts 🔌 typescript TypeScript library labels Nov 16, 2023
nkuba added a commit that referenced this issue Jan 9, 2024
Refs: #749

Here we expose the `revealDepositWithExtraData` function in the `Bridge`
contract. This function allows revealing a deposit whose funding
transaction embeds arbitrary 32-byte extra data.

The new function opens a variety of use cases. An EOA depositor can
craft a Bitcoin deposit transaction that allows a third-party smart
contract to manage the deposit and provide additional services on top.
In this case, 32-byte extra data can be used to securely attribute the
deposit to the original EOA depositor.

### The `revealDepositWithExtraData` function

The `revealDepositWithExtraData` was added next to the existing
`revealDeposit` function to provide backward compatibility of the
`Bridge` contract API. The `Deposit` library that does the heavy lifting
was refactored to handle both regular deposits without extra data and
the new deposits with extra data. The common logic was extracted to the
`_revealDeposit` internal function to avoid duplication.

Worth noting, the extra data passed in the new flow has to be
externalized so tBTC wallets can reconstruct the deposit script and
sweep those deposits, just as the regular ones. To do so, we decided to
store the extra data in the `Bridge` storage (as part of the
`DepositRequest` structure). This approach is not ideal but has some
advantages:
- Negligible gas overhead for regular deposits without extra data (~2000
of gas)
- Easier integration as extra data can be easily fetched from storage by
tBTC wallets and third-party smart contracts
- Making the `Bridge` aware of the extra data may open some use cases in
the future

Alternative approaches rely on emitting the extra data using contract
events. However:
- Extending the existing `DepositReveal` event is not backward
compatible. The event signature would change and clients trying to fetch
past events that don't contain the extra data field would fail. This is
especially problematic for tBTC wallets, SPV maintainers, and block
explorers
- Using a new event just for emitting the extra data is clunky and makes
the integration experience significantly worse

### Changes in the `WalletProposalValidator` contract

The `WalletProposalValidator` contract is used by tBTC off-chain wallet
clients to validate incoming deposit sweep proposals. This contract
needs to reconstruct deposit scripts so must be aware of the new
alternative format with 32-byte extra data. As part of this pull
request, we are adjusting this contract to properly validate deposit
sweep proposals that include deposits with extra data.

### Impact on gas costs and contract size

Before the presented change, the gas costs of functions and size of
affected contract/libraries was as follows:

```
·------------------------------|---------------------------|--------------|-----------------------------·
|     Solc version: 0.8.17     ·  Optimizer enabled: true  ·  Runs: 1000  ·  Block limit: 30000000 gas  │
·······························|···························|··············|······························
|  Methods                                                                                              │
·············|·················|·············|·············|··············|···············|··············
|  Contract  ·  Method         ·  Min        ·  Max        ·  Avg         ·  # calls      ·  eur (avg)  │
·············|·················|·············|·············|··············|···············|··············
|  Bridge    ·  revealDeposit  ·  82767      ·  105463     ·  102057      ·  170          ·  -          │
·············|·················|·············|·············|··············|···············|··············
```
```
·-------------------------------|-------------|---------------·
|  Contract Name                ·  Size (KB)  ·  Change (KB)  │
································|·············|················
|  Bridge                       ·     21.264  ·               │
································|·············|················
|  Deposit                      ·      6.327  ·               │
································|·············|················
|  WalletProposalValidator      ·     11.266  ·               │
································|·············|················
```

After introducing `revealDepositWithExtraData`, those are as follows:

```
·------------------------------·············|---------------------------|--------------|-----------------------------·
|           Solc version: 0.8.17            ·  Optimizer enabled: true  ·  Runs: 1000  ·  Block limit: 30000000 gas  │
············································|···························|··············|······························
|  Methods                                                                                                           │
·············|······························|·············|·············|··············|···············|··············
|  Contract  ·  Method                      ·  Min        ·  Max        ·  Avg         ·  # calls      ·  eur (avg)  │
·············|······························|·············|·············|··············|···············|··············
|  Bridge    ·  revealDeposit               ·  85270      ·  107966     ·  104562      ·  170          ·  -          │
·············|······························|·············|·············|··············|···············|··············
|  Bridge    ·  revealDepositWithExtraData  ·  124176     ·  126773     ·  125872      ·  12           ·  -          │
·············|······························|·············|·············|··············|···············|··············
```
```
·-------------------------------|-------------|---------------·
|  Contract Name                ·  Size (KB)  ·  Change (KB)  │
································|·············|················
|  Bridge                       ·     21.563  ·               │
································|·············|················
|  Deposit                      ·      6.894  ·               │
································|·············|················
|  WalletProposalValidator      ·     11.520  ·               │
································|·············|················
```

### Next steps

This change is the most important one towards full support of deposits
with extra data. However, comprehensive support requires some additional
steps:
- Add support in the tBTC off-chain client
- Add support in the tBTC SDK
nkuba added a commit that referenced this issue Jan 19, 2024
Refs: #749

Pull request #760 introduced
a possibility to embed 32-byte extra data within the deposit script.
This simple change opens multiple possibilities. Notably, third-party
smart contracts can now act as depositor proxies and reveal deposits on
depositors' behalf. This way, proxy contracts receive minted TBTC and
can provide extra services without requiring additional actions from the
depositor (e.g., deposit it to a yield protocol or bridge it to an L2
chain). This, in turn, empowers third-party protocols to use tBTC as a
foundation and propose additional value on top of it. The goal of this
pull request is to facilitate the integration of such third-party
protocols through tBTC SDK.

### The `DepositorProxy` interface

First of all, we are introducing the `DepositorProxy` interface that
represents a third-party depositor proxy contract in a chain-agnostic
way. A third-party integrator willing to relay deposits is expected to
provide an implementation of this interface and inject it into the tBTC
SDK.

The SDK uses the instance of the `DepositorProxy` to prepare the right
deposit script (thus deposit BTC address) and notifies that instance
once the deposit is funded and ready for minting. How minting is
initialized depends on the proxy implementation thus this logic is
abstracted as the `revealDeposit` function exposed by the
`DepositorProxy` interface. This way, the SDK is responsible for the
heavy lifting around deposit construction while the depositor proxy must
only finalize the process by doing their own logic and, reveal the
deposit to the `Bridge` contract.

To facilitate the job for Ethereum-based proxies, we are also exposing
the `EthereumDepositorProxy` abstract class. This component can serve as
a base for classes interacting with Ethereum depositor proxy contracts
that relay deposit data to the Ethereum `Bridge`. The
`EthereumDepositorProxy` aims to make that part easier.

### The `initiateDepositWithProxy` function

To provide a single point of entrance to the depositor proxy flow, we
are exposing the `initiateDepositWithProxy` function. This function is
available from the top-level SDK interface, alongside the regular
`initiateDeposit` function triggering the non-proxy deposit flow. The
signature of the `initiateDepositWithProxy` function is similar to
`initiateDeposit`. The difference is that it expects an instance of the
`DepositProxy` interface. It also accepts optional 32-byte `extraData`
that can be used to embed additional data within the deposit script
(e.g. data allowing to attribute the deposit to the original depositor).
The `initiateDepositWithProxy` returns a `Deposit` object that
represents the initiated deposit process.

### Usage

Here is a brief example illustrating what a third-party integrator
should do to use their contract as a deposit intermediary.
Let's suppose the integrator implemented an `ExampleDepositor` contract
that exposes a `revealDepositOnBehalf` function which takes a deposit
and reveals it to the `Bridge` on behalf of the original depositor:
```typescript
contract ExampleDepositor {
    Bridge public bridge;

    function revealDepositOnBehalf(
        BitcoinTx.Info calldata fundingTx,
        DepositRevealInfo calldata reveal,
        address originalDepositor,
    ) external {
        // Do some additional logic, e.g. attribute the deposit to the original depositor.

        bytes32 extraData = bytes32(abi.encodePacked(originalDepositor));
        bridge.revealDepositWithExtraData(fundingTx, reveal, extraData);
    }
}
```
In that case, the off-chain part leveraging tBTC SDK can be as follows:
```typescript
import {
  BitcoinRawTxVectors,
  ChainIdentifier,
  DepositReceipt,
  EthereumDepositorProxy,
  Hex,
  TBTC,
} from "@keep-network/tbtc-v2.ts"

import { Contract as EthersContract } from "@ethersproject/contracts"
import { JsonRpcProvider, Provider } from "@ethersproject/providers"
import { Signer, Wallet } from "ethers"

// Address of the ExampleDepositor contract.
const contractAddress = "..."
// ABI of the ExampleDepositor contract.
const contractABI = "..."

class ExampleDepositor extends EthereumDepositorProxy {
  // Ethers handle pointing to the ExampleDepositor contract.
  private contractHandle: EthersContract

  constructor(signer: Signer) {
    super(contractAddress)

    this.contractHandle = new EthersContract(
      contractAddress,
      contractABI,
      signer
    )
  }

  revealDeposit(
    depositTx: BitcoinRawTxVectors,
    depositOutputIndex: number,
    deposit: DepositReceipt,
    vault?: ChainIdentifier
  ): Promise<Hex> {
    // Additional logic, if necessary.

    // Prepare parameters for the contract call.
    const { fundingTx, reveal, extraData } = this.packRevealDepositParameters(
      depositTx,
      depositOutputIndex,
      deposit,
      vault
    )

    // Call the depositor contract function that reveals the deposit to the
    // Bridge.
    return this.contractHandle.revealDepositOnBehalf(
      fundingTx,
      reveal,
      this.decodeExtraData(extraData) // Contract function expects originalDepositor as third parameter
    )
  }

  private decodeExtraData(extraData: string): string {
    // Extract the originalDepositor address from extraData.
    // This should be a reverse operation to extra data encoding
    // implemented in the revealDepositOnBehalf function of
    // the ExampleDepositor contract.
    return "..."
  }
}

async function main() {
  // Create a readonly Ethers provider.
  const provider: Provider = new JsonRpcProvider("...")
  // Create an instance of the ExampleDepositor class. Pass an Ethers
  // signer as constructor parameter. This is needed because the
  // ExampleDepositor issues transactions using an Ethers contract handle.
  const exampleDepositor: ExampleDepositor = new ExampleDepositor(
    new Wallet("...", provider)
  )
  // Create an instance of the tBTC SDK. It is enough to pass a readonly
  // Ethers provider as parameter. In this example, the SDK does not issue
  // transactions directly but relies on the ExampleDepositor
  // instead.
  const sdk: TBTC = await TBTC.initializeMainnet(provider)

  // Get BTC recovery address for the deposit.
  const bitcoinRecoveryAddress: string = "..."
  // Determine the address of the original depositor who will actually
  // own the deposit.
  const originalDepositor: string = "..."
  // Encode the original depositor as 32-byte deposit extra data. This
  // must be done in the same way as in the ExampleDepositor solidity contract
  // (see revealDepositOnBehalf function).
  const extraData: Hex = encodeExtraData(originalDepositor)

  // Initiate the deposit with the proxy.
  const deposit = await sdk.deposits.initiateDepositWithProxy(
    bitcoinRecoveryAddress,
    exampleDepositor,
    extraData
  )
  // Get BTC deposit address and send funds to it.
  const depositAddress: string = await deposit.getBitcoinAddress()
  // Initiate minting once BTC transaction is made. This will call
  // revealDepositOnBehalf function of the ExampleDepositor contract
  // under the hood.
  const ethTxHash: Hex = await deposit.initiateMinting()

  console.log(
    `Minting initiated. ETH transaction hash: ${ethTxHash.toPrefixedString()}`
  )
}
```
tomaszslabon added a commit to keep-network/keep-core that referenced this issue Jan 22, 2024
Refs: keep-network/tbtc-v2#749

The `Bridge` contract supports a new shape of the deposit script that
allows embedding arbitrary 32-byte extra data. Here we introduce
necessary adjustments in the wallet client to make it able to sweep
those deposits.
tomaszslabon added a commit that referenced this issue Jan 23, 2024
Refs: #749

The `Bridge` contract was upgraded on Sepolia. A new function
`revealDepositWithExtraData` has been added. Here we update the
deployment artifact embedded in the SDK to make this new function
accessible from there.
@lukasz-zimnoch
Copy link
Member Author

Closing as implementation and testing are done. Leaving the Deployment checklist as a cheat sheet to be used during deployment.

tomaszslabon added a commit to keep-network/keep-core that referenced this issue Jan 25, 2024
Refs: keep-network/tbtc-v2#749

Bitcoin scripts of deposits embedding 32-byte extra data are longer (126
bytes) than the scripts of regular deposits not holding extra data (92
bytes). That means the actual virtual size of the resulting deposit
sweep transaction is greater than the size assumed by the sweep fee
estimator. Here we adjust the fee estimator to always take the longer
script for calculations to avoid underestimation of the transaction fee.
This approach is not ideal but is safe as it makes sure transactions are
not rejected due to too low network fees.
@lukasz-zimnoch lukasz-zimnoch added this to the solidity/v1.6.0 milestone Mar 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
⛓️ solidity Solidity contracts 🔌 typescript TypeScript library
Projects
None yet
Development

No branches or pull requests

2 participants