This repo collects source code, tests, and documentation for the IDEX Whistler release Ethereum contracts.
Download and install nvm, yarn, and python3. Then:
pip3 install slither-analyzer
This repo is setup as a Truffle project, with library and test code written in Typescript. To build:
nvm use
yarn && yarn build
To run test suite, generate coverage report, and perform static analysis:
yarn coverage
yarn analyze
IDEX is in development on a series of major product releases that together comprise IDEX 2.0.
-
Release 1 (Whistler): The Whistler release contains all of the off-chain upgrades of IDEX 2.0, including new web and mobile UIs, new REST and WS APIs, and a high-performance in-memory trading engine that supports advanced order types. Whistler also includes new smart contracts that custody funds and settle trades on-chain. Unlike later releases, the Whistler smart contracts are structurally similar to the IDEX 1.0 contract design in that each trade results in a single layer-1 transaction to update the on-contract balances.
-
Release 2 (Peak2Peak): An original layer-2 scaling solution, known as Optimized Optimistic Rollup (O2R), is part of the larger IDEX 2.0 initiative. Unlike the Whistler contracts, the O2R contracts roll individual trades up into periodic Merkle root state summaries that are published to layer 1. The Peak2Peak release launches the O2R smart contracts and accompanying infrastructure to run in parallel with the Whistler smart contracts. During P2P, the Whistler contracts continue to settle trades and maintain the canonical wallet balances. Running the O2R contracts in parallel allows testing on real workloads and tuning system parameters prior to switching to O2R exclusively.
-
Release 3 (Blackcomb): The Blackcomb release switches settlement and balance tracking from the Whistler contracts to the O2R layer-2 system.
This documentation covers a security audit for the Whistler smart contracts only, with O2R contract audits to follow.
The Whistler on-chain infrastructure includes three main contracts and a host of supporting libraries.
- Custodian: custodies user funds with minimal additional logic.
- Governance: implements upgrade logic while enforcing governance constraints.
- Exchange: implements the majority of exchange functionality, including wallet asset balance tracking.
Whistler supports trading Ethereum and ERC-20 tokens, and requires users to deposit Eth and tokens into the Whistler smart contracts before trading. The interaction lifecycle spans three steps.
Users must deposit funds into the Whistler contracts before they are available for trading on IDEX. Depositing ETH
requires calling depositEther
on the Exchange contract; depositing tokens requires an approve
call on the token itself
before calling depositTokenByAddress
on the Exchange contract.
-
The
depositEther
anddepositTokenByAddress
are functions on the Exchange contract, but the funds are ultimately held in the Custody contract. As part of the deposit process, tokens are transferred first to the Exchange contract, which tracks wallet asset balances, and then transferred again to the Custody contract. Separate exchange logic and fund custody supports IDEX 2.0’s upgrade design. -
Deposits are only allowed for registered tokens.
-
Deposit amounts are adjusted to IDEX 2.0’s normalized precision design to prevent depositing any dust.
-
Deposits from exited wallets are rejected.
In Whistler, all order management and trade matching happens off-chain while trades are ultimately settled on-chain. A
trade is considered settled when the Exchange contract’s wallet asset balances reflect the new values agreed to in the
trade. Exchange’s executeTrade
function is responsible for settling trades.
-
Unlike deposits, trade settlement can only be initiated via a whitelisted Dispatch wallet controlled by IDEX. Users do not settle trades directly; only IDEX can submit trades for settlement. Because IDEX alone controls dispatch, IDEX’s off-chain components can guarantee eventual on-chain trade settlement order and thus allow users to trade in real-time without waiting for dispatch or mining.
-
The primary responsibility of the trade function is order and trade validation. In the case that IDEX off-chain infrastructure is compromised, the validations ensure that funds can only move in accordance with orders signed by the depositing wallet.
-
Due to business requirements, orders are specified by symbol, eg “UBT-ETH” rather than by token contract addresses. A number of validations result from the token symbol registration system. Note the
trade
parameter to theexecuteTrade
function includes the symbol strings separately. This is a gas optimization to order signature verification as string concat is cheaper than split. -
Due to business requirements, order quantity and price are specified as strings in PIP precision, hence the need for order signature validation to convert the provided values to strings.
-
IDEX 2.0 supports partial fills on orders, which requires additional bookkeeping to prevent overfills and replays.
-
Fees are assessed as part of trade settlement. The off-chain trading engine computes fees, but the trade function is responsible for enforcing that fees are within previously defined limits. Business rules require that makers and takers are charged different fees. Fees are deducted from the quantity of asset each party is receiving.
Similar to trade settlement, withdrawals are initiated by users via IDEX’s off-chain components, but calls to the
Exchange contract’s withdraw
function are restricted to whitelisted Dispatch wallets. withdraw
calls are limited to the
Dispatch wallet in order to guarantee the balance update sequence and thus support trading ahead of settlement. There
is also a wallet exit
mechanism to prevent withdrawal censorship by IDEX.
-
Withdrawals may be requested by asset symbol or by token contract address. Withdrawal by asset symbol is the standard approach as dictated by business rules and requires a lookup of the token contract address in the token symbol registry. Withdrawal by token contract asset exists to cover the case where an asset has been relisted under the same symbol, for example in the case of a token swap.
-
IDEX collects fees on withdrawals in order to cover the gas costs of the
withdraw
function call. Because only an IDEX-controlled Dispatch wallet can make thewithdraw
call, IDEX is the immediate gas payer for user withdrawals. IDEX passes along the estimated gas costs to users by collecting a fee out of the withdrawn amount. -
Despite the
withdraw
function being part of the Exchange contract, funds are returned to the user’s wallet from the Custody contract.
Upon the Whistler release, IDEX users must withdraw funds from the IDEX 1.0 contract and deposit funds into the Whistler contract to continue trading. For an improved UX going forward, Whistler’s contracts include upgrade logic that enables the rollout of the subsequent Blackcomb release without requiring users to withdraw and redeposit funds into a new Custody contract. The upgrade logic is minimalist by design.
-
The Custody contract tracks the Governance contract and Exchange contract. The Governance contract is the only actor authorized to change the Custody contract’s Governance or Exchange target, and implements the rules under which such changes may be made.
-
Exchange state data is stored in the Exchange contract itself. Because state data, such as wallet asset balances, is not held in an external contract, any upgrade to the Exchange contract requires actively migrating the state data to the upgraded contract.
-
The anticipated target of the upgrade is the Blackcomb release’s O2R layer-2 system, where the Exchange state data will be moved off chain going forward.
The Whistler controls and governance design is captured in its own spec.
Business rules require orders to be specified in asset symbol terms rather than token contract address terms. For
example, an order specifies the market as "UBT-ETH"
rather than { "base": "0xb705268213d593b8fd88d3fdeff93aff5cbdcfae", "quote": "0x0" }
. Deposits, withdrawals and asset balance tracking, however, must be implemented in token contract
address terms. In order to support both usage modes, Whistler includes a token registry that maps symbols to token contract
addresses along with additional token metadata, such as precision. Only registered tokens are accepted for deposit.
-
Token registration is a two-transaction process, requiring separate calls to
registerToken
andconfirmTokenRegistration
. Two steps reduce the likelihood of data entry errors when registering a new token. -
Occasionally projects upgrade their token address via a token swap but need to retain the same trading symbol. To support this use case, the token registration mechanism can track multiple token contract addresses for a symbol. The registry includes registration time stamps to ensure orders and withdrawals are only executed against the intended token contract address, as validated against the order or withdrawal nonce. Off-chain business process rules ensure orders are not accepted during new token registration of the same symbol to prevent race conditions.
In its off-chain components, IDEX 2.0 normalizes all assets to a maximum of 8 decimals of precision, with 1e-8 referred
to as a "pip". Because deposit and withdrawals must account for the true token precision, however, the token registry
includes token decimals as well as functions to convert pipsToAssetUnits
and assetUnitsToPips
. All wallet asset
balances are tracked in pips.
Orders include nonces to prevent replay attacks. IDEX 2.0 uses version-1 UUIDs as nonces, which include a timestamp as part of the value.
IDEX’s hybrid off-chain/on-chain architecture is vulnerable to a cancelled-order submission attack if the off-chain components are compromised. In this scenario, an attacker gains access to the Dispatch wallet and a set of cancelled orders by compromising the off-chain order book. Because the orders themselves include valid signatures from the placing wallet, the Whistler contract cannot distinguish between active orders placed by users and those the user has since cancelled.
Nonce invalidation via invalidateOrderNonce
allows users to invalidate all orders prior to a specified nonce, making it
impossible to submit those orders in a subsequent cancelled-order submission attack. The
controls and governance spec covers the exact mechanics and parameters of the mechanism.
Whistler includes a wallet exit mechanism, which allows users to withdraw funds in the case IDEX is offline or
maliciously censoring withdrawals. Calling exitWallet
initiates the exit process, which prevents
the wallet from subsequent deposits, trades, or normal withdrawals. Wallet exits are a two-step process as defined in
controls.
The IDEX Whistler Smart Contracts and related code are released under the GNU Lesser General Public License v3.0.