# Numo ### An automated market maker that sells binary options Numo is a non-custodial, algorithmic counterpary to binary option traders. Built as Uniswap V4 hook, Numo enables traders to permissionlessly create a binary options market on any token on the EVM. ## Implementation Numo is suite of smart contracts in Solidity. The primary contract,`Lendgine.sol` follows a ERC-20 standard and houses all the core logic to enable borrowing and lending of Numo `amm` instances. The contract inherits the interface `JumpRate` which provides the functions for calculating the variables funding rates on any borrowed position, the `Pair` interface for creating a pair, and the interface `ILendgine`. All positions are repersented by a mapping `mapping(address => Position.Info);` which is data structure for storing the `Position` as the key and the data that is associated with a position as the value. From each position, the following information can be retrieved: * `balance`: user's balance of the Lendgine token * `borrowedBalance`: The amount of the Lengine token that the user has borrowed. * `interestOwed`: The amount of interest that the user owes on their borrowed balance. `ERC-20` is an interface that offers functions for transferring and approving the transfer of Lendgine tokens. `JumpRateModel` is the interface that provides the function for calculating the variables funding rates on any borrowed position. `Pair` is the interface that offers functions for managing pairs as well as creating a pair through `create3` opcode for which the same factory address corresponds to the same pair across deployments. ### Lendgine Storage State variables are variables that are stored permanently on the blockchain.In the case of Lendgine, the following are written to storage: `positions`: A mapping from user addresses to their correspinding position information `totalPositionSize`: Total size of all positions in the lendgine AMM. `totalLiquidityBorrowe` The first function `mint()` enables a perpetual position to minted to `address` when collateral is deposited. ## Market Model The black-scholes trading function: \begin{equation} \psi(R_1, R_2) = R_1 - \Phi\left( \Phi^{-1}(1 - K R_2) - \sigma \sqrt{\tau} \right) \end{equation} The cumulative distribution function $\Phi$ of the std normal distribution. The function uses the inverse CDF to find the value of the x such that $\Phi(x)$ The function is implemented in the `lendgine.sol` contract in the function `invariant()` as: ``` // @inheritdoc IPair function invariant(uint256 amount0, uint256 amount1, uint256 liquidity) public view override returns (bool) { if (liquidity == 0) return (amount0 == 0 && amount1 == 0); uint256 scale0 = FullMath.mulDiv(amount0 * token0Scale, 1e18, liquidity); uint256 scale1 = FullMath.mulDiv(amount1 * token1Scale, 1e18, liquidity); if (scale1 > 4 * upperBound) revert InvariantError(); uint256 a = scale0 * 1e18; uint256 b = scale1 * upperBound; uint256 c = (scale1 * scale1) / 4; uint256 d = upperBound * upperBound; return a + b >= c + d; } ``` The function `invariant()` checks whether the pool reserves satifies the following invariant condition: ``` a + b >= c + d ``` where: The function then uses the calculated scaling factors and the upper bound to perform an algebraic check. It defines variables: `a`: First token amount multiplied by 1e18 (scaling factor for the first token). `b`: Second token amount multiplied by the upper bound. `c`: Square of the second token scaling factor divided by 4. `d`: Square of the upper bound. Finally, it returns true if the sum of a and b is greater than or equal to the sum of c and d. This specific formula represents the invariant condition for this particular smart contract, likely related to the relationship between token amounts and liquidity. `a` is the product of scale0 and 1e18 `b` is the product of scale1 and upperBound `c` is the product of scale1 and scale1, divided by 4 `d` is the product of upperBound and upperBound The scale0 and scale1 variables are calculated by dividing the amount0 and amount1 variables by the liquidity variable, and then multiplying by 1e18. The upperBound variable is a constant that defines the maximum value that the scale1 variable can take. If the invariant condition is not satisfied, the function will revert with an InvariantError. The @inheritdoc IPair annotation at the top of the function indicates that this function is overriding the invariant() function from the IPair interface. ## Lending Mechanism The current design uses a jump rate model in the `JumpController.sol` contract. It works by setting a target ultization and using that inform the following four paramters: - **Kink**: Where the funding jumps Since the optimal funding is the realized volitlty of the underlying asset, it can never go negative. The target ultization is the same as asking what the optimial funding rate for a quadratic perpetual might be, known as theta. This Funding rates in a traditional market works by having (MARK - SPOT). Since these perpetuals are not synethic, they're the funding is calculated differently. The deviation of linear perps from the spot price expresses contango/backwardation (in particular, an exponential-decay-weighted integral over dated futures contango). The deviation of quadratic perps from spot expresses contango plus willingness to pay for theta (IV, if you will). Indeed, if we denote the price of ETH at time t as P_t, then the non-discounted "premium" for a dated ETH^2 future should be ``` E[P_expiry^2] - P_now^2 = Var[P_expiry] + E[P_expiry]^2 - P_now^2 = Var[P_expiry] + 2 * P_now * C + C^2 ``` where C = E[P_expiry] - P_now expresses contango. Of course, we have to ask ourselves whether the perpetual price converges - if variance is finite and the exponential decay rate implied by the funding rate is at least twice the exponential growth rates of price and variance, then it should. Remark: It's convenient that we can price this with only mean and variance, the most natural things for a pricing model to include (for example, Black-Scholes). One way to see why the funding rate should be positive by looking at the payoff to just holding ETH²: Payoff to ETH² = (ETH +(change in ETH))²-ETH² = 2 *ETH * (change in ETH)+ (change in ETH)² The fundamental theorem of asset pricing tells us that the correct funding rate equals the expected value of this payoff (technically to make it a risk-neutral measure, but more plainly it means that the contract price should fully reflect expectations about the price). The first term is 2x the funding cost from the future (which will typically be positive) and the second term — the second moment of ETH price changes — will always be positive. ELI5 version: the funding rate is positive because squared things have to be positive! Disclaimer: This article is not financial advice. Instead it should be viewed as performance art. ### Spot trading fee ## Returns Looking at power perpetual returns = (1+r)² = r² + 2r + 1 Looking at quartic perpetual returns = (1+r)^4 = r^4 + 4r^3 + 3r^2 + 2r + 1 # References: https://colab.research.google.com/drive/1mrxubKiFUhlavol4a38NJYaANdSNAEun https://medium.com/galois-capital/algorithmic-trading-in-crypto-430431da1e0a https://llllvvuu.dev/blog/unbundling-gamma https://github.com/waynenilsen/zendax/blob/master/latex/PowerClaims.pdf https://medium.com/friktion-research/power-perpetuals-eli5-part-1-4fd3eee56310 https://hackernoon.com/kurtosis-and-bitcoin-uc55n3w5x PID https://medium.com/reflexer-labs/summoning-the-money-god-2a3f3564a5f2 https://ethereum.org/en/developers/tutorials/uniswap-v2-annotated-code/ https://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdf https://uniswap.org/whitepaper.pdf https://betterprogramming.pub/uniswap-v2-in-depth-98075c826254 https://llllvvuu.dev/blog/raw-moments ``` function invariant(uint256 amount0, uint256 amount1, uint256 liquidity) public view override returns (bool) { if (liquidity == 0) return (amount0 == 0 && amount1 == 0); // No scaling factors or upper bound check needed for the new formula uint256 x = amount0; // Assuming amount0 represents 'x' in the formula uint256 y = amount1; // Assuming amount1 represents 'y' in the formula // Raise y to the power of 4/3 using ** operator (assumingSafeMath is used for overflow protection) uint256 y_raised = y ** (4 / 3); // Calculate -3/4 * y_raised uint256 negative_three_fourths_y_raised = y_raised * 3 / 4; // Check the new invariant condition return x >= negative_three_fourths_y_raised ** (4 / 3); } ```