Try   HackMD

Anchor Vault + BridgeConnectorWormhole audit

Anchor Vault is a smart contract that allows to convert rebasing stETH token into a constant-balance bETH token and periodically send all accrued stETH rewards to the Terra blockchain through a bridge. The bETH token is used as a collateral in the Terra Anchor protocol.

The Anchor Vault is deployed & functions already, and was audited by MixBytes() on July, 30th, 2021. The main reason for the audit request is the coming migration from the Shuttle bridge to the Wormhole. The Vault's bridge_connector would be set to BridgeConnectorWormhole contract (to be deployed).

Scope of the audit

Commit: https://github.com/lidofinance/anchor-collateral-steth/commit/8d52ce72cb42d48dff1851222e3b624c941ddb30

Contracts

Deployed contracts & Deployment

The contracts aside from the BridgeConnectorWormhole are deployed to the Ethereum mainnet:

Currently integration uses the Shuttle bridge for cross-chain interactions. The bridge connector contract is deployed there:

Deployment script with post-deployment tests for the current integration can be found here: https://github.com/lidofinance/anchor-collateral-steth/blob/8d52ce72cb42d48dff1851222e3b624c941ddb30/scripts/final_check.py.

Wormhole bridge connector deployment scripts can be found here:

Tests

Tests are located in https://github.com/lidofinance/anchor-collateral-steth/tree/8d52ce72cb42d48dff1851222e3b624c941ddb30/tests. Note that the Wormhole bridge connector tests are here:
https://github.com/lidofinance/anchor-collateral-steth/blob/8d52ce72cb42d48dff1851222e3b624c941ddb30/tests/test_connector_wormhole.py

Architecture

RewardsLiquidator, InsuranceConnector and BridgeConnectorWormhole contracts are installed as delegates to the AnchorVault contract and are used by the latter for performing various tasks (see the description above). These contracts can be replaced by the vault admin.

The BridgeConnectorWormhole contract is designed to interoperate with the Wormhole Ethereum-Terra bridge. The Bridge contract source code can be found here: https://github.com/certusone/wormhole/blob/9bc408ca1912e7000c5c2085215be9d44713028b/ethereum/contracts/bridge/Bridge.sol

Vault mechanics

bETH issuance

Minting bETH: a user can submit stETH to the vault contract and receive the corresponding amount of freshly-minted bETH tokens which get immediately transferred to a user-specified Terra address via the bridge. The received stETH tokens are locked inside the vault contract.

Burning bETH: a user can burn their bETH tokens (which should be already on the Ethereum side) in exchange for stETH tokens from the vault’s balance.

The amount of bETH minted per one stETH locked, as well as the amount of stETH released per one bETH burnt, is determined by the current bETH/stETH rate. Normally, this rate is 1, but it might become less than 1 if Lido validators get penalized or slashed. If that happens, the only way of recovering the rate would be applying protocol-wide insurance. The rate cannot be greater than 1.

stETH rebases

stETH is a rebasable token. Balance of each address is internally represented in non-normalized shares, where total number of shares is not bounded, and stETH balance is calculated from shares balance as follows:

steth_balance(address) = shares_balance(address) * share_price
share_price = total_lido_controlled_ether / total_shares

Rebases are performed as a result of Lido oracles reporting Beacon chain rewards or penalties, which results in total_lido_controlled_ether changing (and hence balances of all stETH holders increasing or decreasing). This happens once in ~24h.

A rebase might also be triggered by applying protocol-wide insurance, which is done by minting stETH in exchange for ETH (which proportionally increases both total_lido_controlled_ether and total_shares) and burning the minted stETH shares right away, which results in total_shares decreasing and share_price increasing. The total number of shares burnt to apply insurance since the protocol inception is tracked by the protocol and stored onchain.

stETH rewards handling

All increases in AnchorVault’s stETH balance that result from Beacon rewards should be periodically sold to UST (Anchor-issued stablecoin) and forwarded to a pre-defined Terra address via the bridge.

Balance increases resulting from insurance applications should NOT be sold to UST and should instead result in recovering bETH/stETH rate.

stETH rebases happen at most once in 24h. Once a rebase has occured, the vault should prohibit submitting and withdrawing stETH until the rewards accrued since the last sell are sold to UST and forwarded to Terra. It should be impossible to sell rewards twice within 24h.

BridgeConnectorWormhole methods & details

Method _transfer_asset() is an internal method to transfer required assets from Ethereum blockchain to Terra blockchain via Wormhole token bridge. Aside from common arguments, there is additional _extra_data argument to provide ability to submit arbitrary bytes. Leading 64 bytes are reserved to pass nonce and arbiter_fee to comply the Wormhole token bridge interface while maintaining backward compatibility for AnchorVault instance.

Method forward_beth() transfers bETH to Terra chain by using _transfer_asset() with predefined values.

Method forward_ust() transfers UST to Terra chain by using _transfer_asset() with predefined values.

Method adjust_amount() allowing to adjust amount of assets to transfer and comply with bridge limitations for fractioning.

Failure modes

  1. Rewards collection is impossible if the current pool prices differ significantly from the ones reported by price oracles. In the event of oracle failure or Ethereum network congestion, rewards collection may be delayed for an indeterminate amount of time.

  2. Conversion between stETH and bETH is impossible after stETH has rebased and between AnchorVault.collect_rewards() call. Normally, an offchain rewards collection bot performs such a call as soon as it detects a rebase. Thus:

    • If the bot becomes inoperable, the vault would be deemed unusable until `AnchorVault.collect_rewards()`` call is manually made or the bot is revived.
    • If stETH undergoes a rebase sooner than 24h after the previous one, the vault would be deemed unusable until 24h passes after the previous rebase and AnchorVault.collect_rewards() call is made.
    • In the event of Ethereum network congestion or price oracle failure, the vault might be deemed unusable until the situation resolves since rewards collection would fail (see 1).