Request for Public Feedback: Ethereum Bifröst

Evolving a plan for connecting to Ethereum

Request for Public Feedback: Ethereum Bifröst

Overview

The team are building the Ethereum Bifröst and look for public input into its design.

There are multiple ways to achieve the design, but they have ramifications for user-experience, optimisations and DeFi composability.

Bifröst Design Goals

The Bifröst is simply a chain-specific connection. It includes a module that is equipped to scan the chain and make observations (the Observer), as well as a module to handle outgoing transaction requests (the Signer).

Observer & Witness Transactions

The Observer is tasked to watch vault addresses and bundle any relevant incoming transfers into a witness transaction for THORChain. The witness transaction passes the following standardised information:type Tx struct {
 ID          TxID    `json:"id"`
 Chain       Chain   `json:"chain"`
 FromAddress Address `json:"from_address"`
 ToAddress   Address `json:"to_address"`
 Coins       Coins   `json:"coins"`
 Gas         Gas     `json:"gas"`
 Memo        string  `json:"memo"`
}

For the case of Ethereum, the chain is identified as ETH whilst the assets will be identified as ETH.SYMBOL-CONTRACTADDRESS such as:

  • ETH: ETH.ETH
  • USDT: ETH.USDT-0xdac17f958d2ee523a2206206994597c13d831ec7

The memo passes through transaction intent, such as the following staking, swapping and withdrawing transactions:

stake:ETH.USDT-0xdac17f958d2ee523a2206206994597c13d831ec7

swap:ETH.USDT-0xdac17f958d2ee523a2206206994597c13d831ec7:

withdraw:ETH.USDT-0xdac17f958d2ee523a2206206994597c13d831ec7

Signer & Outgoing Transactions

The Signer receives transaction orders txOut from the THORChain state machine. These look like the following:type TxOutItem struct {
 Chain       Chain   `json:"chain"`
 ToAddress   Address `json:"to"`
 VaultPubKey PubKey  `json:"vault_pubkey"`
 Coin        Coin    `json:"coin"`
 Memo        string  `json:"memo"`
 InHash      TxID    `json:"in_hash"`
 OutHash     TxID    `json:"out_hash"`
}

They are completely chain-agnostic, simply referring to the relevant chain once again as its identifier. It is the job of the Signer to bundle this into a valid transaction object for each chain and sign it. Initially a new txOutItem has an empty out_hash, since it has not been processed yet. The signer places the in_hash in the memo, such as:outbound:0xb24c6f1f8281dcb54b77a4676c03bd2b0ddd8b4823327485f9f1c0e46919c439

The outgoing memo is important, because it will allow the Observer to locate it on the chain, observe it once more, and then create a new witness transaction containing it. The state machine will recognise the memo and place the hash in as the out_hash. This allows the accounting to be complete, and will generate an event recording it.

Ethereum Nuances

The above design will work without problem with just ether ETH.ETH — the memo containing the transaction intent can be passed in via ethereum’s data-field. However, there are some issues when considering dealing with ERC-20 transfers.

Firstly, ERC-20 smart contracts are essentially silo’ed mini-databases on Ethereum that contain a mapping of token balances and addresses. When a transfer happens, a user simply authorises the ERC-20 smart contract to decrement their balance and increment the recipient’s balance. An event is created if successful. There is no single global registry of a user’s address and all the ERC-20 token balances associated with that address. Services such as Etherscan and Ethplorer are actually aggregrators — they monitor all block data and look for ALL ERC-20 transfers that concern every known address.

Secondly, ERC-20 transfer authorisations are passed in via a zero-ether transaction and its data-field — taking up the entire field with the following informationMethodID: 0xa9059cbb
[0]:  0000000000000000000000006262998ced04146fa42253a5c0af90ca02dfd2a3
[1]:  000000000000000000000000000000000000000000000000000000003bb94e80

The MethodID is simply the keccak256 hash of the transfer function. The array passed are the two inputs to the function itself:transfer(address _to, uint256 _value)

Problems

This design leads to the following problems:

  1. All ERC-20 transfers must be prepared prior to the transaction taking place, in order to pass in the transaction intent (swap, stake, withdraw).
  2. The Observer needs to monitor at great detail each block, in order to find relevant transactions.
  3. The signer cannot batch outbound ERC-20 transfers.
  4. Outbound ERC-20 transfers cannot be readily identified since there is nowhere to place the txOut memo.

Additionally, the prospect of DeFi composability becomes unlikely, since it would be difficult for a DeFi smart contract to know where the vault address is and interact with it.

Solutions

There are two main solutions. The first is “Vault-as-TSS-Address”, the second is “Vault-as-TSS-Owned-Smart-Contract”.

Vault-as-TSS-Address

The first solution is to encode the TSS public key into a valid ethereum address and use it to receive and process outgoing transactions. The user will use an ether transaction to pre-register transaction intent ahead of time from their address. The Observer will do thorough analysis of block content, looking at all ERC-20 transfer events and filtering out those of relevance. The Signer will do single ERC-20 outgoing transactions, and the Observer will identify the transactions by looking at the transaction content (address, amount, asset). The Signer could even use a channel to tell the Observer ahead of time which transaction hash is associated with each txOut. This solution, whilst viable, has all the problems mentioned above, but it would still work regardless.

Vault-as-TSS-Owned-Smart-Contract

The second solution is to encode the TSS public key into a valid ethereum address and use it as the “Owner” of an owned smart contract. The user will use the typical “Approve/TransferFrom” flow to register their transaction intent. The vault smart contract will serve as a single registry for all ERC-20 transfers, allowing the Observer an easy way to locate relevant transactions. The Signer can batch an array of outgoing transactions and process them by calling a transfer function on the smart contract that can handle arrays. The Signer can record the memo against each outgoing transaction (including batched ones) allowing the Observer to easily identify.

Additionally, the system gains DeFi composability by allowing other smart contracts to call the deposit function and process swaps, even to other networks. However, this would be a “fire-and-forget” approach, since the system would not be able to inform the originating smart contract of the success of a swap in the same block. Whilst partially limited, this would be sufficient to allow liquidations of assets by smart contracts, including liquidation cross-chain.

Other considerations, such as updating the owner, are relatively simple, since the TSS owner would update the smart contract ownership simply with a valid transaction. Owned smart contracts (using the onlyOwner modifier), are well-known primitive in Ethereum and have a limited attack surface as long as the implementation is correct.

Public Feedback

The team are looking for public feedback into the Ethereum Bifröst design and welcome all input. Both solutions above still respect the chain-agnostic behaviour of THORChain and don’t require any changes. The logic is handled purely in the Bifröst module.

Please reach out to the team via channels below and discuss. The team would love to hear from the Ethereum community.


Community

To keep up to date, please monitor community channels, particularly Telegram and Twitter: