|
| 1 | +// amm_pool.cell - Constant Product AMM Liquidity Pool |
| 2 | + |
| 3 | +module cellscript::acceptance::amm_pool |
| 4 | + |
| 5 | +use cellscript::fungible_token::Token |
| 6 | + |
| 7 | +/// LP (Liquidity Provider) receipt |
| 8 | +receipt LPReceipt has store { |
| 9 | + pool_id: Hash |
| 10 | + lp_amount: u64 |
| 11 | + provider: Address |
| 12 | +} |
| 13 | + |
| 14 | +shared Pool has store { |
| 15 | + token_a_symbol: [u8; 8] |
| 16 | + token_b_symbol: [u8; 8] |
| 17 | + reserve_a: u64 |
| 18 | + reserve_b: u64 |
| 19 | + total_lp: u64 |
| 20 | + fee_rate_bps: u16 |
| 21 | +} |
| 22 | + |
| 23 | +action seed_pool( |
| 24 | + token_a: Token, |
| 25 | + token_b: Token, |
| 26 | + fee_rate_bps: u16, |
| 27 | + provider: Address |
| 28 | +) -> (Pool, LPReceipt) { |
| 29 | + assert_invariant(token_a.symbol != token_b.symbol, "same token") |
| 30 | + assert_invariant(token_a.amount > 0 && token_b.amount > 0, "zero liquidity") |
| 31 | + assert_invariant(fee_rate_bps <= 10000, "fee too high") |
| 32 | + |
| 33 | + let initial_lp = isqrt(token_a.amount * token_b.amount) |
| 34 | + |
| 35 | + consume token_a |
| 36 | + consume token_b |
| 37 | + |
| 38 | + let pool = create Pool { |
| 39 | + token_a_symbol: token_a.symbol, |
| 40 | + token_b_symbol: token_b.symbol, |
| 41 | + reserve_a: token_a.amount, |
| 42 | + reserve_b: token_b.amount, |
| 43 | + total_lp: initial_lp, |
| 44 | + fee_rate_bps |
| 45 | + } |
| 46 | + |
| 47 | + let receipt = create LPReceipt { |
| 48 | + pool_id: pool.type_hash(), |
| 49 | + lp_amount: initial_lp, |
| 50 | + provider |
| 51 | + } with_lock(provider) |
| 52 | + |
| 53 | + (pool, receipt) |
| 54 | +} |
| 55 | + |
| 56 | +action swap_a_for_b( |
| 57 | + pool: &mut Pool, |
| 58 | + input: Token, |
| 59 | + min_output: u64, |
| 60 | + to: Address |
| 61 | +) -> Token { |
| 62 | + assert_invariant(input.symbol == pool.token_a_symbol, "wrong input token") |
| 63 | + |
| 64 | + let fee = input.amount * pool.fee_rate_bps as u64 / 10000 |
| 65 | + let net_input = input.amount - fee |
| 66 | + |
| 67 | + let output = pool.reserve_b * net_input / (pool.reserve_a + net_input) |
| 68 | + |
| 69 | + assert_invariant(output >= min_output, "slippage exceeded") |
| 70 | + assert_invariant(output < pool.reserve_b, "insufficient reserves") |
| 71 | + |
| 72 | + consume input |
| 73 | + |
| 74 | + pool.reserve_a = pool.reserve_a + input.amount |
| 75 | + pool.reserve_b = pool.reserve_b - output |
| 76 | + |
| 77 | + create Token { |
| 78 | + amount: output, |
| 79 | + symbol: pool.token_b_symbol |
| 80 | + } with_lock(to) |
| 81 | +} |
| 82 | + |
| 83 | +action add_liquidity( |
| 84 | + pool: &mut Pool, |
| 85 | + token_a: Token, |
| 86 | + token_b: Token, |
| 87 | + provider: Address |
| 88 | +) -> LPReceipt { |
| 89 | + assert_invariant(token_a.symbol == pool.token_a_symbol, "wrong token a") |
| 90 | + assert_invariant(token_b.symbol == pool.token_b_symbol, "wrong token b") |
| 91 | + |
| 92 | + let lp_from_a = token_a.amount * pool.total_lp / pool.reserve_a |
| 93 | + let lp_from_b = token_b.amount * pool.total_lp / pool.reserve_b |
| 94 | + let lp_amount = min(lp_from_a, lp_from_b) |
| 95 | + |
| 96 | + consume token_a |
| 97 | + consume token_b |
| 98 | + |
| 99 | + pool.reserve_a = pool.reserve_a + token_a.amount |
| 100 | + pool.reserve_b = pool.reserve_b + token_b.amount |
| 101 | + pool.total_lp = pool.total_lp + lp_amount |
| 102 | + |
| 103 | + create LPReceipt { |
| 104 | + pool_id: pool.type_hash(), |
| 105 | + lp_amount, |
| 106 | + provider |
| 107 | + } with_lock(provider) |
| 108 | +} |
| 109 | + |
| 110 | +action remove_liquidity( |
| 111 | + pool: &mut Pool, |
| 112 | + receipt: LPReceipt, |
| 113 | + provider: Address |
| 114 | +) -> (Token, Token) { |
| 115 | + assert_invariant(receipt.pool_id == pool.type_hash(), "wrong pool") |
| 116 | + |
| 117 | + let amount_a = receipt.lp_amount * pool.reserve_a / pool.total_lp |
| 118 | + let amount_b = receipt.lp_amount * pool.reserve_b / pool.total_lp |
| 119 | + |
| 120 | + consume receipt |
| 121 | + |
| 122 | + pool.reserve_a = pool.reserve_a - amount_a |
| 123 | + pool.reserve_b = pool.reserve_b - amount_b |
| 124 | + pool.total_lp = pool.total_lp - receipt.lp_amount |
| 125 | + |
| 126 | + let token_a = create Token { |
| 127 | + amount: amount_a, |
| 128 | + symbol: pool.token_a_symbol |
| 129 | + } with_lock(provider) |
| 130 | + |
| 131 | + let token_b = create Token { |
| 132 | + amount: amount_b, |
| 133 | + symbol: pool.token_b_symbol |
| 134 | + } with_lock(provider) |
| 135 | + |
| 136 | + (token_a, token_b) |
| 137 | +} |
| 138 | + |
| 139 | +action isqrt(n: u64) -> u64 { |
| 140 | + if n == 0 { |
| 141 | + return 0 |
| 142 | + } |
| 143 | + |
| 144 | + let mut x = n |
| 145 | + let mut y = (x + 1) / 2 |
| 146 | + |
| 147 | + while y < x { |
| 148 | + x = y |
| 149 | + y = (x + n / x) / 2 |
| 150 | + } |
| 151 | + |
| 152 | + x |
| 153 | +} |
| 154 | + |
| 155 | +action min(a: u64, b: u64) -> u64 { |
| 156 | + if a < b { a } else { b } |
| 157 | +} |
0 commit comments