In order to integrate Vega with various external blockchains to allow the deposit and withdrawal of settlement instruments (assets) into and out of the Vega network, we’ve determined that a set of “bridge” smart contracts along with an ‘event queue’ process to find and propagate applicable on-chain events is necessary for the deposit and withdrawal of funds/assets from Vega. This, collectively, is named the Vega Ramp as it is the on- and off-ramp of all assets regardless of chain of origin. This spec covers the bridges.
This document outlines how assets will be able to be deposited from and withdrawn to Ethereum addresses. This will be achieved with a few new moving parts, outlined below:
graph TD
A[Ethereum Bridge Contract] --> B
C[ERC20 Bridge Contract] --> B[Event Queue]
D[ERC721 Bridge Contract] -->|Not in V1| B[Event Queue]
B --> F[Vega node]
F --> G[Vega chain]
There will be one smart contract per Ethereum asset class (such as ETH or ERC-20 tokens). We will consider new token standards as they are used, and develop new smart contracts to handle those. Regardless of asset type, the new Event Bus will be polling an Ethereum node for events on the known smart contracts, and pushing those in to Vega through a new API.
In order to enable decentralised and secure depositing and withdrawal of funds, we have created a series of “bridge” smart contracts. These bridges each target a specific asset class, such as ETH or ERC-20 tokens, and expose simple functionality to allow the Vega network to accept deposits, hold, and then release assets as needed. This immutably records all deposits and withdrawals for all of the assets that Vega markets use, as well as any governance pertaining to the bridge smart contracts. Each bridge contains two primary functions and emits two primary events, each tailored to the asset class. They are deposit and withdraw and the corresponding events of deposited and withdrawn. Deposit is ran by a user or process and ensures that the asset is stored safely on-contract and then emits the deposited event. The withdrawal function itself is run by the user or process once signatures have been aggregated from validator nodes. This multisig aggregation is out of the scope of this specification and will be covered elsewhere.
It used to be normal behaviour when validating transfers to wait a certain number of blocks for a deposit to be 'confirmed'. With the new version of Ethereum, an additional mechanism exists that assures finality of a block that can be used instead, which leads to cleaner results.
We use this mechanism for all Ethereum related finality requirements. For legacy reasons ,there is still a parameter defining the number of confirmations previously used, but this parameter is ignored now.
graph TD
A[User]-->|Runs Deposit function with Vega public key|B
B[Bridge Smart Contract] -->|Emits Deposit event|C
C[Event Queue]-->|Filters and forwards applicable event|D
D[Vega Consensus]-->|Checks event acceptance status|C
A-->|Requests Withdrawal and aggregates signatures from validators |D
A-->|Submits signatures to Withdrawal function to receive funds|B
For each asset class, there is a bridge smart contract. Currently all contracts are Ethereum-based assets, namely Ether and ERC-20 tokens. In the future ERC-721 nonfungible tokens, ERC1155 crypto items, and Oracle Controlled Assets (assets that are reported by an authority) and other asset classes will be added. Each asset class will receive a bridge contract on the appropriate platform (ETH, EOS, Tron, etc).
Deposits happen when a user runs the deposit function of a bridge contract for a given asset. Once this is executed on-chain, an event is raised from the Ethereum protocol. This event is processed by the event queue (covered in another spec) which passes the event to Vega Consensus. Each node receives notice of the event either from the Event Queue or through inter-node gossip and validates the transaction for itself on its local external blockchain node (such as Geth, Parity, etc). This necessitates each node to either run a given blockchain node locally or have a trusted source to the node.
Withdrawals happen when a user decides to withdrawal funds from Vega and/or Vega consensus decides release an asset to a user. When this happens, the client aggregates signatures from the validator nodes (covered elsewhere). Once a threshold of signatures is reached, the client runs the withdraw_asset
command while providing the bundle of authorised signatures.
Each bridge implements a standard interface (Ethereum shown here):
pragma solidity ^0.5.0;
contract IVega_Bridge {
event Asset_Withdrawn(address indexed user_address, address indexed asset_source, uint256 indexed asset_id, uint256 amount);
event Asset_Deposited(address indexed user_address, address indexed asset_source, uint256 indexed asset_id, uint256 amount, bytes32 vega_public_key);
event Asset_Deposit_Minimum_Set(address indexed asset_source, uint256 indexed asset_id, uint256 new_minimum);
event Asset_Whitelisted(address indexed asset_source, uint256 indexed asset_id);
event Asset_Blacklisted(address indexed asset_source, uint256 indexed asset_id);
event Multisig_Control_Set(address indexed multisig_control_source);
function whitelist_asset(address asset_source, uint256 asset_id, uint256 nonce, bytes memory signatures) public;
function blacklist_asset(address asset_source, uint256 asset_id, uint256 nonce, bytes memory signatures) public;
function set_deposit_minimum(address asset_source, uint256 asset_id, uint256 nonce, uint256 minimum_amount, bytes memory signatures) public;
function withdraw_asset(address asset_source, uint256 asset_id, uint256 amount, uint256 nonce, bytes memory signatures) public;
function deposit_asset(address asset_source, uint256 asset_id, uint256 amount, bytes32 vega_public_key) public;
function set_multisig_control(address new_multisig_contract_address) public;
// VIEWS /////////////////
function is_asset_whitelisted(address asset_source, uint256 asset_id) public view returns(bool);
function get_deposit_minimum(address asset_source, uint256 asset_id) public view returns(uint256);
function get_multisig_control_address() public view returns(address);
}
The ERC-20 contract, and any other contract that represents an asset class rather than an individual asset, will maintain a whitelist of assets that can and cannot be deposited. Only allow listed assets can be deposited.
- An asset that is on the allowed list can be withdrawn and deposited
- An asset that is not on the allowed list can be withdrawn but not deposited
Allow listing an asset occurs through a governance decision on the Vega chain. Eventually a user will be in possession of a bundle of signatures that they will send to the smart contract, along with the contact address of the asset to be whitelisted. After this has been accepted on the Ethereum chain, events for that asset will start being sent through to nodes via the Event Bus.
The list of assets that are allow listed could be inferred by looking through the chain at asset-related governance decisions, but the duty of storing the allowed list is with the Ethereum bridge smart contract, which stores the list on the ethereum chain. This list is also available through watching for the list_asset
event raised by the smart contract.
Block listing is simply removing an asset from the whitelist via the remove_asset
function.
The Ethereum Bridge uses 1 network parameter, blockchains.ethereumConfig
, a JSON value which must configure:
Property | Type | Example value | Description |
---|---|---|---|
chain_id |
String | "3" |
Ethereum Chain ID to connect to |
network_id |
String | "3" |
Ethereum Network ID to connect to |
collateral_bridge_contract |
{string, integer} | {address: "0xCcB517899f714BD1B2f32931fF429aDEdDd02A93", deployment_height: 1} |
The address for a deployed instance of the bridge contract |
staking_bridge_contract |
{string, integer} | {address: "0xCcB517899f714BD1B2f32931fF429aDEdDd02A93", deployment_height: 1} |
The addresses to listen to for staking events. |
token_vesting_contract |
{string, integer} | {address: "0xCcB517899f714BD1B2f32931fF429aDEdDd02A93", deployment_height: 1} |
The addresses to listen to for vesting contract events. |
multisig_control_contract |
{string, integer} | {address: "0xCcB517899f714BD1B2f32931fF429aDEdDd02A93", deployment_height: 1} |
The addresses to listen to for multisig control event |
confirmations |
Integer | 3 |
Block confirmations to wait for before confirming an action (legacy) |
This example connects the network to Ropsten:
{
"network_id": "3",
"chain_id": "3",
"collateral_bridge_contract": {address: "0xCcB517899f714BD1B2f32931fF429aDEdDd02A93", deployment_height: 1},
"staking_bridge_contract": {address: "0xCcB517899f714BD1B2f32931fF429aDEdDd02A93", deployment_height: 1},
"token_vesting_contract": {address: "0xCcB517899f714BD1B2f32931fF429aDEdDd02A93", deployment_height: 1},
"multisig_control_contract": {address: "0xCcB517899f714BD1B2f32931fF429aDEdDd02A93", deployment_height: 1},
"confirmations": 3
}
- ERC-20 smart contract (This can be repeated for many token standards - NFTs and crypto items will be more complex):
- A wallet address can call the deposit function on the bridge contract on any EVM/ERPC chain on which the bridge contract is deployed and successfully deposit any token that is listed via
list_asset
on the asset bridge on the said chain (0031-ETHB-084). - A deposit call with a removed token
remove_asset
is rejected (0031-COSMICELEVATOR-009)
- A wallet address can call the deposit function on the bridge contract on any EVM/ERPC chain on which the bridge contract is deployed and successfully deposit any token that is listed via
- ERC-20 smart contract specific requirements:
- A valid multisig bundle can be passed to the withdraw function to successfully withdraw an ERC-20 asset to a wallet address on any EVM/ERPC chain asset bridge (0031-ETHB-085)
- An invalid multisig bundle will be rejected from withdraw (0031-ETHB-014)
- ERC-20 smart contract specific requirements:
- A valid multisig bundle can be passed to the
list_asset
function to successfully add a token to the allowed list (0031-ETHB-016) - An invalid multisig bundle is rejected by the
list_asset
function (0031-ETHB-017)
- A valid multisig bundle can be passed to the
To ensure complete coverage of public and external smart contract functions, listed below are all of the callable functions on ERC20_Bridge_Logic
and their corresponding acceptance criteria.
address payable public erc20_asset_pool_address;
- must match the deployed asset pool address (0031-ETHB-018)
function list_asset(address asset_source,bytes32 vega_asset_id,uint256 lifetime_limit,uint256 withdraw_threshold,uint256 nonce,bytes memory signatures)
- must list asset (0031-ETHB-019)
- must not list already-listed asset (0031-ETHB-020)
- must not list if bad signatures (0031-ETHB-021)
- must not list if already listed (0031-ETHB-022)
function remove_asset(address asset_source,uint256 nonce,bytes memory signatures)
- must remove asset (0031-ETHB-023)
- must fail if asset not listed (0031-ETHB-024)
- must fail if bad signatures (0031-ETHB-025)
uint256 public default_withdraw_delay = 432000;
- must show 432000 (0031-ETHB-026)
bool public is_stopped;
- must be false at first (0031-ETHB-027)
- must be true after
global_stop
called (0031-ETHB-028)
function set_asset_limits(address asset_source,uint256 lifetime_limit,uint256 threshold,uint256 nonce,bytes calldata signatures)
- changes asset limits (0031-ETHB-029)
- must fail if bad signatures (0031-ETHB-030)
- asset must be listed (0031-ETHB-031)
function get_asset_deposit_lifetime_limit(address asset_source)
- must return asset lifetime limit (0031-ETHB-032)
function get_withdraw_threshold(address asset_source)
- must return withdraw threshold (0031-ETHB-033)
function set_withdraw_delay(uint256 delay,uint256 nonce,bytes calldata signatures)
- must set withdraw delay (0031-ETHB-034)
- must fail if bad signatures (0031-ETHB-036)
function global_stop(uint256 nonce, bytes calldata signatures)
- must set
is_stopped
totrue
(0031-ETHB-037) - must stop deposits (0031-ETHB-038)
- must stop withdrawals (0031-ETHB-039)
- must not be stopped already (0031-ETHB-040)
- must fail on bad signatures (0031-ETHB-041)
- must set
function global_resume(uint256 nonce, bytes calldata signatures)
- must set
is_stopped
tofalse
(0031-ETHB-042) - must resume deposits (0031-ETHB-043)
- must resume withdrawals (0031-ETHB-044)
- must already be stopped (0031-ETHB-045)
- must fail on bad signatures (0031-ETHB-046)
- must set
function exempt_depositor()
- must exempt caller from lifetime deposits (0031-ETHB-047)
- must not be already exempt (0031-ETHB-048)
function revoke_exempt_depositor()
- must revoke lifetime deposit exemption (0031-ETHB-049)
- must already be exempt (0031-ETHB-050)
function is_exempt_depositor(address depositor) external view override returns (bool)
- must show lifetime deposit exemption (0031-ETHB-051)
function withdraw_asset(address asset_source,uint256 amount,address target,uint256 creation,uint256 nonce,bytes memory signatures)
- must withdraw asset specified (0031-ETHB-052)
- must fail on bad signatures (0031-ETHB-053)
- must fail on non-matching parameters (0031-ETHB-054)
- must fail on expired withdrawal order (0031-ETHB-082)
- must fail on delay not yet elapsed (0031-ETHB-083)
function deposit_asset(address asset_source,uint256 amount,bytes32 vega_public_key)
- must deposit asset from user (0031-ETHB-055)
- must be listed (0031-ETHB-056)
- must be exempt or below lifetime deposit limit (0031-ETHB-057)
- must fail if over lifetime limit and not exempt (0031-ETHB-058)
- must be credited on Vega (0031-ETHB-059)
function is_asset_listed(address asset_source) external view override returns (bool)
- must be true if asset is listed (0031-ETHB-060)
- must be false if asset is not listed (0031-ETHB-061)
- when true, deposits must work (0031-ETHB-062)
- when false deposits must not work (0031-ETHB-063)
function get_multisig_control_address() external view override returns (address)
- must show deployed multisig address (0031-ETHB-064)
function get_vega_asset_id(address asset_source) external view override returns (bytes32)
- must return proper Vega asset ID (0031-ETHB-065)
function get_asset_source(bytes32 vega_asset_id) external view override returns (address)
- must return the deployed asset address from Vega asset ID (0031-ETHB-066)
To ensure complete coverage of public and external smart contract functions, listed below are all of the callable functions on ERC20_Asset_Pool and their corresponding acceptance criteria.
address public multisig_control_address;
- must show the current multisig control address (0031-ETHB-067)
- must change to reflect a successful set_multisig_control call (0031-ETHB-068)
address public erc20_bridge_address;
- must show current deployed
erc20_bridge
address (0031-ETHB-069) - must change to reflect a successful set_bridge_address call (0031-ETHB-070)
- must show current deployed
receive() external payable // fallback, should fail
function set_multisig_control(address new_address,uint256 nonce,bytes memory signatures)
- must set multisig control (0031-ETHB-071)
- must be reflected in
multisig_control_address
(0031-ETHB-072) - must fail on bad signatures (0031-ETHB-073)
function set_bridge_address(address new_address,uint256 nonce,bytes memory signatures)
- must set bridge address (0031-ETHB-074)
- must be reflected in
erc20_bridge_address
(0031-ETHB-075) - must fail on bad signatures (0031-ETHB-076)
function withdraw(address token_address,address target,uint256 amount)
- must remit the
amount
oftoken_address
to thetarget
address (0031-ETHB-077) - must be runnable from the current
erc20_bridge_address
address (0031-ETHB-078) - must fail if ran by any other address (0031-ETHB-079)
- must work for new bridge after bridge address changed (0031-ETHB-080)
- must fail for old bridge after bridge address changed (0031-ETHB-081)
- must remit the