Architecture

This page details some the important process in the PingPong exchange as well as some key formulas , interfaces and general contract structure

Conceptual Overview

This overview might be at odds with the code. If you spot something like that please let us know so we can fix it.

Variable Definitions

For simplicity and as reference point here is a more detailed definition of all variables and short names used throughout the description.

  • Market - A contract that facilitates swaps between ETH and an ERC20 token and vice versa.

  • k - The invariant of the AMM

  • S - The percentage difference between a price level N and the one above it

  • SwapFee - The fee (in percentages) payed buy order takers/traders

  • floor - The nearest price level below the current rate of the AMM

  • ciel - The nearest price level above the current rate of the AMM

Depositing Liquidity to AMM

This part is identical to how existing AMMs work. Liquidity providers deposit amounts of ETH and TOKEN that maintain the existing ratio in the exchange. In return they get liquidity shares equal to:

Setting Up Limit Orders

A harder consideration is how do you set up limit orders. The current design enables users to send ETH to the floor level and TOKEN to the ciel level. In return they get withdrawal tokens at a 1 to 1 ratio to the tokens they need to receive as a result from the trade. minted to them. These tokens guarantee that after a level has been completely passed then anyone who deposited an asset can withdraw the appropriate amount of the other asset. It's up to the user to actualize his withdrawal tokens and move them to her own wallet. Alternatively, the user can call watchers that can withdraw user tokens on their behalf.

/**
@notice Takes a level number and deposits either tokens or ether to it.
Determining which should it be is done according to the current AMM ratio.
If sent to level below would expect ETH and if sent to a level above would expect TOKEN
@param level Level that the sender wishes to fill
@param tokenAmount Amount of tokens if applicable the sender wishes to put in
@param watcher Address of watcher who can complete the depositors trade on his behalf
@param watcherFee Fee to pay the watcher to compensate him for withdrawl
*/
function depositToLevel(uint256 level, uint256 tokenAmount, address watcher, uint32 watcherFee) public payable returns(bool);

/**
@notice Enables a user or an approved watcher to withdraw the assets after a trade is 
@param level Level that the sender wishes to withdraw from
@param address Beneficiary of the withdrawl
@param withdrawlTokenAmount Amount of tokens to withdraw
*/
function withdrawFromLevel(uint256 level, address beneficiery, uint256 withdrawlTokenAmount) public returns(bool);

Performing Trades

Performing a trade has a few stages, first the trader goes to the AMM and pays a fee on the whole trade. The trader then buys tokens/eth in the AMM until they hit a price wall. They then buy tokens/eth from the price wall until the wall is dry after which they go back to the AMM. This process can continue until the desired take amount is fulfilled.

In the following section we'll provide a flow of performing a sell of X tokens to eth.

  1. If the ciel is not enough to fill the SellAmount then repeat steps 2-4

A similar process can be performed for Selling eth and buying eth and tokens

Order of entering a limit order

The current design is that ignores the order in which market makers set up limit orders. It is entirely possible that someone who put their order first will end up withdrawing the trade proceeds last as because they were late to respond.

We could potentially compnsate early entrants by minting less withdraw tokens the more tokens already exist at the the level:

The tradeoffs of increased complexity versus potentially better incentive are still being explored.

Contract Structuring

Each TOKEN/ETH pair is a market consisting of an unlimited number of contracts.

Automated Market Maker

This is a single regular contract (not a library, due to the high amount of transactions to the contract not worth the delegate call overhead) serving as the entry point to the DEX.

It shares a lot of it's functionality with the traditional AMM with some of the main changes outlined below.

PingPongAMM.sol
contract AutomatedMarketMaker{

uint256 ciel;
uint256 floor;
mapping(uint256 => address) ciels;
mapping (uint256 => address) floors;

/**
@notice As an example of a trade function
This function receives eth and a desired number of tokens as return.
It returns the tokens desired and the remainder of the eth. Making sure do go through the designated ciels.
@param outputAmount Number of tokens to return
@param deadline max time to execute the trade
*/
function ethToTokenOutput(uint256 outputAmount, uint256 deadline) public payable; 

/**
@notice If a ciel for paticular rate doesn't exist create it
Optionally if msg.value > 0 automaticall mint sender withdrawl tokens
@param rate rate to create the ciel for
@param watcher address for newly minted withdrawl tokens watcher
@param watcherFee fee for watcher
*/
function createNewCiel(uint rate, address watcher, uint watcherFee) public payable;

}

It stores AMM reserves and liquidity tokens and shares most of it's functionality with existing AMM. The exepction of course is that the trading takes into account the EthTillCiel/TokenTillFloor amounts defined earlier and calls the Ciel/Floor contracts accordingly.

Floor/Ciel Contracts

Each market has multiple Floor/Ciel contracts that proxy their logic into a shared master contract. The core of each logic an be summed into the following

CielLevel.sol
contract CielLevel{
    address token;
    uint256 rate;
    mapping(address => uint256) withdrawlTokens;
    mapping(address => address => uint) watcherFees;
    
    /**
    @notice Mints rate * msg.value withdrawal tokens to the sender
    @param watcher watcher for user tokens
    @param watcherFee fee for watcher
    */
    function makeOrder(address watcher, uint watcherFee) public payable;
    
    /**
    @notice Cancels ethAmount out of the order and returns the eth to the msg.sender
    @param ethAmount Amount ot cancel 
    */
    function cancelOrder(uint ethAmount) public;
    
    /**
    @notice Exchanges the withdraw tokens for actual tokens
    @param amount amount of tokens to exchange
    */
    function exchangeTokens(amount) public;
}

A similar contract exists for the Floor levels.

Gas Cost Estimation

Other than some relatively cheap calculations the potential additional gas costs are:

  • 20K more for each level involved

  • 40K (or maybe 20K depends on cost of exponentiation) for each time a level is passed

Last updated