Skip to main content
Full working code: The complete source code for this example is available in test-contracts/amm/
This example builds on top of the Token contract and demonstrates swaps and liquidity provision on an AMM. It demonstrates transfers of balances using the token-dyn interface for cross-contract calls to the Token contract, and complex integer arithmetic using the 256-bit Integer built-in type.
Standards Pattern: This AMM accepts any token contract address that implements the transfer interface. The compiler ensures the AMM makes valid calls to that interface, but cannot verify at compile time that user-provided addresses actually implement it.

WIT Interface

Imports types:
  • signer: Transaction sender (identity)
  • error: Failure handling
  • integer: Amounts
and exports functions:
  • init: Initializes the contract
  • create: Create a liquidity pool for a new token pair
  • fee: Get the fee (in basis points of input amount) for trading in a pair
  • balance: Queries a liquidity provider (LP) share balance
  • token-balance: Queries the balance of tokens in a liquidity pool
  • deposit: Deposit both tokens of a pair and receive LP shares
  • withdraw: Burn LP shares and withdraw the tokens they represent
  • swap: Swap an amount of one token of a pair for the other token
  • quote-deposit: Receive a quote of cost of shares received when depositing
  • quote-withdraw: Receive a quote of tokens received for shares when withdrawing
  • quote-swap: Receive a quote of out-tokens received when swapping

Rust Implementation

  • AMMStorage represents the contract’s persistent storage. The StorageRoot derive macro enables storage capabilities for the type and marks it as the “root” storage type. The root storage is accessible via a generated function: storage, which provides an ORM-like interface to contract functions. Every contract with storage must have exactly one type that derives StorageRoot.
  • Map is a storage-enabled mapping type that can hold values of any type that also derive Storage. Holds mappings from token pairs to Pool.
  • Pool contains the information about the state of the pool for a token pair.
  • The ctx parameter must be passed to every operation that performs a database call. These functions take an impl ReadContext or an impl WriteContext, depending on their behavior. ProcContext implements both, while ViewContext implements only ReadContext. This ensures state mutations occur only as the result of a transaction while allowing the same functions and methods to be used across procedures and views.

Testing

Similar to the tests for other contracts, this one uses the interface! macro to generate an interface for calling the contract. It also includes assertions for error handling. For all calls, the first ? operator checks whether the runtime threw an error during execution. For contract functions that explicitly return a Result, the result can be “unwrapped” with an additional ? operator or left omitted to make an assertion on an error. The test publishes three contracts: the amm contract and two separate instances of the token contract to serve as the trading pair. When working with numbers in Sigil, either the Integer or Decimal types should be used. From instances have been implemented for many of the primitive types which is why there are many <num>.into()s in the test. One can also write: Integer::from(100), or Decimal::from("1.5"), or even let x: Decimal = 1.5.into(), etc.