From 0863e9aaa4b492f1df5f3feb0e2d22370bed9f71 Mon Sep 17 00:00:00 2001 From: Shubhendu Shekhar Date: Mon, 31 May 2021 17:27:14 +0200 Subject: [PATCH 01/92] fix: check undefined instead of zero --- frontend/app/src/components/OvenActions/MintOrBurn.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/src/components/OvenActions/MintOrBurn.tsx b/frontend/app/src/components/OvenActions/MintOrBurn.tsx index d5ebffb6..146cb210 100644 --- a/frontend/app/src/components/OvenActions/MintOrBurn.tsx +++ b/frontend/app/src/components/OvenActions/MintOrBurn.tsx @@ -50,7 +50,7 @@ export const MintOrBurn: React.FC = ({ type }) => { .min(0.000001) .test({ test: (value) => { - if (value && drift && currentTarget && type === 'mint') { + if (value && typeof drift !== 'undefined' && currentTarget && type === 'mint') { const newOutstanding = Number(ctez_outstanding) + value * 1e6; const tez = Number(tez_balance); const result = isMonthFromLiquidation(newOutstanding, currentTarget, tez, drift); From 2c4e29f8132f3b533afd7c59296da7410b7c5a4e Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Thu, 5 Aug 2021 15:26:59 +0100 Subject: [PATCH 02/92] New difference equations on token_to_cash and cash_to_token. --- cfmm.mligo | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 4fef0f16..3d2007c3 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -293,6 +293,38 @@ let cash_transfer (storage : storage) (from : address) (to_ : address) (cash_amo #endif #endif +// Returns +let price_x_to_y (x : nat) (y : nat) : nat = + let x2 = x * x in + let y2 = y * y in + let num = y * (3n * x2 + y2) in + let denom = x * (x2 + 3n * y2) in + num/denom + +// A function to transfer assets along a curve in https://hackmd.io/MkPSYXDsTf-giBprcDrc3w +// In our case: a, b = 1 and dy = (price_x_to_y x y) * dx +// TODO : Transaction fees +let rec newton_x_to_y (a:nat) (b:nat) (x:nat) (y:nat) (dx:nat) (dy_approx:nat) = + let ax = a * x and by = b * y in + let ax2 = ax * ax and by2 = by * by in + (* todo yp could be negative *) + let xp = x + dx and yp = y - dy_approx in + let axp = a * xp and byp = b * yp in + let num = x * y * (ax2 + by2) - xp * yp * (axp2 + byp2) + and denom = xp * (axp2 + 3n * byp2) in + let adjust = num / denom in + (* todo: check if condition always met *) + if (abs adjust) <= 1 then + dy_approx - 1 (* better to be a bit stingy *) + else + newton_x_to_y a b x y dx (dy - adjust) + +// A function that outputs dy given x, y, and dx +let trade_x_for_y (x:nat) (y:nat) (dx:nat) = + let current_price = price_x_to_y x y in + let dy_approx = current_price * cashSold in + newton_x_to_y 1n 1n x y dx dy_approx + (* ============================================================================= * Entrypoint Functions * ============================================================================= *) @@ -423,7 +455,8 @@ let cash_to_token (param : cash_to_token) (storage : storage) = unless all liquidity has been removed. *) let cashPool = storage.cashPool in let tokens_bought = - (let bought = (cashSold * const_fee * storage.tokenPool) / (cashPool * const_fee_denom + (cashSold * const_fee)) in + // cash -> token calculation; *includes a fee* + (bought = trade_x_for_y cashPool storage.tokenPool cashSold in if bought < minTokensBought then (failwith error_TOKENS_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_TOKENS_BOUGHT : nat) else @@ -465,8 +498,9 @@ let token_to_cash (param : token_to_cash) (storage : storage) = else (* We don't check that tokenPool > 0, because that is impossible unless all liquidity has been removed. *) + // token -> cash calculation; *includes a fee* let cash_bought = - let bought = ((tokensSold * const_fee * storage.cashPool) / (storage.tokenPool * const_fee_denom + (tokensSold * const_fee))) in + let bought = trade_x_for_y storage.tokenPool storage.cashPool tokensSold in if bought < minCashBought then (failwith error_CASH_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_CASH_BOUGHT : nat) else bought in let op_token = token_transfer storage Tezos.sender Tezos.self_address tokensSold in From 603c0b8a7ebe7a5f295c58da0ffa1111e4d095fb Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Fri, 6 Aug 2021 11:14:16 +0100 Subject: [PATCH 03/92] Reorganize code for and add comments for readability. --- ctez.mligo | 52 +++++++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/ctez.mligo b/ctez.mligo index 9598d66d..a42c42b7 100644 --- a/ctez.mligo +++ b/ctez.mligo @@ -41,6 +41,7 @@ type storage = { } type result = (operation list) * storage +(* Errors *) [@inline] let error_OVEN_ALREADY_EXISTS = 0n [@inline] let error_INVALID_CALLER_FOR_OVEN_OWNER = 1n @@ -60,24 +61,7 @@ type result = (operation list) * storage #include "oven.mligo" -let create (s : storage) (create : create) : result = - let handle = { id = create.id ; owner = Tezos.sender } in - if Big_map.mem handle s.ovens then - (failwith error_OVEN_ALREADY_EXISTS : result) - else - let (origination_op, oven_address) : operation * address = - create_oven create.delegate Tezos.amount { admin = Tezos.self_address ; handle = handle ; depositors = create.depositors } in - let oven = {tez_balance = Tezos.amount ; ctez_outstanding = 0n ; address = oven_address} in - let ovens = Big_map.update handle (Some oven) s.ovens in - ([origination_op], {s with ovens = ovens}) - -let set_addresses (s : storage) (addresses : set_addresses) : result = - if s.ctez_fa12_address <> ("tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" : address) then - (failwith error_CTEZ_FA12_ADDRESS_ALREADY_SET : result) - else if s.cfmm_address <> ("tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" : address) then - (failwith error_CFMM_ADDRESS_ALREADY_SET : result) - else - (([] : operation list), {s with ctez_fa12_address = addresses.ctez_fa12_address ; cfmm_address = addresses.cfmm_address}) +(* Functions *) let get_oven (handle : oven_handle) (s : storage) : oven = match Big_map.find_opt handle s.ovens with @@ -97,6 +81,33 @@ let get_oven_delegate (oven_address : address) : (key_hash option) contract = | None -> (failwith error_OVEN_MISSING_DELEGATE_ENTRYPOINT : (key_hash option) contract) | Some c -> c +let get_ctez_mint_or_burn (fa12_address : address) : (int * address) contract = + match (Tezos.get_entrypoint_opt "%mintOrBurn" fa12_address : ((int * address) contract) option) with + | None -> (failwith error_CTEZ_FA12_CONTRACT_MISSING_MINT_OR_BURN_ENTRYPOINT : (int * address) contract) + | Some c -> c + + +(* Entrypoint Functions *) + +let create (s : storage) (create : create) : result = + let handle = { id = create.id ; owner = Tezos.sender } in + if Big_map.mem handle s.ovens then + (failwith error_OVEN_ALREADY_EXISTS : result) + else + let (origination_op, oven_address) : operation * address = + create_oven create.delegate Tezos.amount { admin = Tezos.self_address ; handle = handle ; depositors = create.depositors } in + let oven = {tez_balance = Tezos.amount ; ctez_outstanding = 0n ; address = oven_address} in + let ovens = Big_map.update handle (Some oven) s.ovens in + ([origination_op], {s with ovens = ovens}) + +let set_addresses (s : storage) (addresses : set_addresses) : result = + if s.ctez_fa12_address <> ("tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" : address) then + (failwith error_CTEZ_FA12_ADDRESS_ALREADY_SET : result) + else if s.cfmm_address <> ("tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" : address) then + (failwith error_CFMM_ADDRESS_ALREADY_SET : result) + else + (([] : operation list), {s with ctez_fa12_address = addresses.ctez_fa12_address ; cfmm_address = addresses.cfmm_address}) + let withdraw (s : storage) (p : withdraw) : result = let handle = {id = p.id ; owner = Tezos.sender} in let oven : oven = get_oven handle s in @@ -123,11 +134,6 @@ let register_deposit (s : storage) (p : register_deposit) : result = let ovens = Big_map.update p.handle (Some oven) s.ovens in (([] : operation list), {s with ovens = ovens}) -let get_ctez_mint_or_burn (fa12_address : address) : (int * address) contract = - match (Tezos.get_entrypoint_opt "%mintOrBurn" fa12_address : ((int * address) contract) option) with - | None -> (failwith error_CTEZ_FA12_CONTRACT_MISSING_MINT_OR_BURN_ENTRYPOINT : (int * address) contract) - | Some c -> c - (* liquidate the oven by burning "quantity" ctez *) let liquidate (s: storage) (p : liquidate) : result = let oven : oven = get_oven p.handle s in From 485ef4102b4b5f36e1e00cc5134921ea63a0f73c Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Mon, 9 Aug 2021 10:49:29 +0100 Subject: [PATCH 04/92] Add entrypoint to cfmm contract to update target, which affects the equation for the CFMM. --- cfmm.mligo | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 3d2007c3..628b8715 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -71,6 +71,8 @@ type set_baker = } #endif +type ctez_target = nat + (* getbalance update types for fa12 and fa2 *) type update_fa12_pool = nat type update_fa2_pool = ((address * nat) * nat) list @@ -92,6 +94,7 @@ type update_cash_pool_internal = update_fa12_pool type entrypoint = | AddLiquidity of add_liquidity | RemoveLiquidity of remove_liquidity +| Ctez_target of ctez_target | CashToToken of cash_to_token | TokenToCash of token_to_cash | TokenToToken of token_to_token @@ -118,6 +121,8 @@ type storage = { tokenPool : nat ; cashPool : nat ; lqtTotal : nat ; + target : ctez_target ; + ctez_address : address ; pendingPoolUpdates : nat ; #if HAS_BAKER freezeBaker : bool ; @@ -200,7 +205,7 @@ type mintOrBurn = [@inline] let error_BAKER_PERMANENTLY_FROZEN = 22n [@inline] let error_LQT_ADDRESS_ALREADY_SET = 24n [@inline] let error_CALL_NOT_FROM_AN_IMPLICIT_ACCOUNT = 25n -(* 26n *) +[@inline] let error_CALLER_MUST_BE_CTEZ = 26n (* 27n *) #if TOKEN_IS_FA2 [@inline] let error_INVALID_FA2_TOKEN_CONTRACT_MISSING_BALANCE_OF = 28n @@ -293,7 +298,7 @@ let cash_transfer (storage : storage) (from : address) (to_ : address) (cash_amo #endif #endif -// Returns +// Returns the price ∆y/∆x at a given point (x,y) let price_x_to_y (x : nat) (y : nat) : nat = let x2 = x * x in let y2 = y * y in @@ -322,7 +327,7 @@ let rec newton_x_to_y (a:nat) (b:nat) (x:nat) (y:nat) (dx:nat) (dy_approx:nat) = // A function that outputs dy given x, y, and dx let trade_x_for_y (x:nat) (y:nat) (dx:nat) = let current_price = price_x_to_y x y in - let dy_approx = current_price * cashSold in + let dy_approx = current_price * dx in newton_x_to_y 1n 1n x y dx dy_approx (* ============================================================================= @@ -434,6 +439,14 @@ let remove_liquidity (param : remove_liquidity) (storage : storage) : result = end end +let ctez_target (param : ctez_target) (storage : storage) = + if Tezos.sender <> storage.ctez_address then + (failwith error_CALLER_MUST_BE_CTEZ : result) + else + let updated_target = param in + let storage = {storage with target = updated_target} in + (([] : operation list), storage) + let cash_to_token (param : cash_to_token) (storage : storage) = let { to_ = to_ ; @@ -745,6 +758,8 @@ let main ((entrypoint, storage) : entrypoint * storage) : result = add_liquidity param storage | RemoveLiquidity param -> remove_liquidity param storage + | Ctez_target param -> + ctez_target param storage #if HAS_BAKER | SetBaker param -> set_baker param storage From 9dba0a9cbb134891c4ad9c4af2da0e23071a3fb5 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Mon, 9 Aug 2021 12:05:37 +0100 Subject: [PATCH 05/92] Fix price and difference equations --- cfmm.mligo | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 628b8715..1ae8ae6d 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -71,7 +71,8 @@ type set_baker = } #endif -type ctez_target = nat +(* (a,b) such that a/b is the target price from the ctez contract *) +type ctez_target = nat * nat (* getbalance update types for fa12 and fa2 *) type update_fa12_pool = nat @@ -298,18 +299,19 @@ let cash_transfer (storage : storage) (from : address) (to_ : address) (cash_amo #endif #endif -// Returns the price ∆y/∆x at a given point (x,y) -let price_x_to_y (x : nat) (y : nat) : nat = - let x2 = x * x in - let y2 = y * y in - let num = y * (3n * x2 + y2) in - let denom = x * (x2 + 3n * y2) in +// Returns the price dy/dx, i.e. a map by multiplication ∆x => ∆y, at a given point (x,y) +let price_x_to_y (target : nat * nat) (target_b : nat) (x : nat) (y : nat) : nat = + let (a,b) = target in + let ax2 = x * x * a * a in + let by2 = y * y * b * b in + let num = y * (3n * ax2 + by2) in + let denom = x * (ax2 + 3n * by2) in num/denom // A function to transfer assets along a curve in https://hackmd.io/MkPSYXDsTf-giBprcDrc3w -// In our case: a, b = 1 and dy = (price_x_to_y x y) * dx // TODO : Transaction fees -let rec newton_x_to_y (a:nat) (b:nat) (x:nat) (y:nat) (dx:nat) (dy_approx:nat) = +let rec newton_x_to_y (target : nat * nat) (x:nat) (y:nat) (dx:nat) (dy_approx:nat) = + let (a,b) = target in let ax = a * x and by = b * y in let ax2 = ax * ax and by2 = by * by in (* todo yp could be negative *) @@ -325,10 +327,18 @@ let rec newton_x_to_y (a:nat) (b:nat) (x:nat) (y:nat) (dx:nat) (dy_approx:nat) = newton_x_to_y a b x y dx (dy - adjust) // A function that outputs dy given x, y, and dx -let trade_x_for_y (x:nat) (y:nat) (dx:nat) = - let current_price = price_x_to_y x y in +let trade_dx_for_dy (target : nat * nat) (x : nat) (y : nat) (dx : nat) = + let current_price = price_x_to_y target x y in let dy_approx = current_price * dx in - newton_x_to_y 1n 1n x y dx dy_approx + newton_x_to_y target x y dx dy_approx + +// A function that outputs dx given target, x, y, and dy +let trade_dy_for_dx (target : nat * nat) (x : nat) (y : nat) (dy : nat) = + let (a,b) = target in + let target_inv = (b,a) in + let current_price = price_x_to_y target_inv y x in + let dx_approx = current_price * dy in + newton_x_to_y target_inv y x dy dx_approx (* ============================================================================= * Entrypoint Functions From d47229a407a0efb8e5396a97ef1864fa2e52d50f Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Mon, 9 Aug 2021 12:28:13 +0100 Subject: [PATCH 06/92] Add transaction fees to token_to_cash and cash_to_token. --- cfmm.mligo | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 1ae8ae6d..63966e92 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -18,7 +18,7 @@ type add_liquidity = [@layout:comb] { owner : address ; (* address that will own the minted lqt *) - minLqtMinted : nat ; (* minimum number of lqt that must be minter *) + minLqtMinted : nat ; (* minimum number of lqt that must be minted *) maxTokensDeposited : nat ; (* maximum number of tokens that may be deposited *) #if !CASH_IS_TEZ cashDeposited : nat ; (* if cash isn't tez, specifiy the amount to be deposited *) @@ -123,6 +123,7 @@ type storage = cashPool : nat ; lqtTotal : nat ; target : ctez_target ; + const_fee : nat * nat ; ctez_address : address ; pendingPoolUpdates : nat ; #if HAS_BAKER @@ -299,8 +300,9 @@ let cash_transfer (storage : storage) (from : address) (to_ : address) (cash_amo #endif #endif +(* In all the following calculations, cash is x, tokens are y *) // Returns the price dy/dx, i.e. a map by multiplication ∆x => ∆y, at a given point (x,y) -let price_x_to_y (target : nat * nat) (target_b : nat) (x : nat) (y : nat) : nat = +let price_cash_to_token (target : nat * nat) (target_b : nat) (x : nat) (y : nat) : nat = let (a,b) = target in let ax2 = x * x * a * a in let by2 = y * y * b * b in @@ -309,7 +311,6 @@ let price_x_to_y (target : nat * nat) (target_b : nat) (x : nat) (y : nat) : nat num/denom // A function to transfer assets along a curve in https://hackmd.io/MkPSYXDsTf-giBprcDrc3w -// TODO : Transaction fees let rec newton_x_to_y (target : nat * nat) (x:nat) (y:nat) (dx:nat) (dy_approx:nat) = let (a,b) = target in let ax = a * x and by = b * y in @@ -326,14 +327,14 @@ let rec newton_x_to_y (target : nat * nat) (x:nat) (y:nat) (dx:nat) (dy_approx:n else newton_x_to_y a b x y dx (dy - adjust) -// A function that outputs dy given x, y, and dx -let trade_dx_for_dy (target : nat * nat) (x : nat) (y : nat) (dx : nat) = +// A function that outputs dy (diff_token) given x, y, and dx +let trade_dcash_for_dtoken (target : nat * nat) (x : nat) (y : nat) (dx : nat) = let current_price = price_x_to_y target x y in let dy_approx = current_price * dx in newton_x_to_y target x y dx dy_approx -// A function that outputs dx given target, x, y, and dy -let trade_dy_for_dx (target : nat * nat) (x : nat) (y : nat) (dy : nat) = +// A function that outputs dx (diff_cash) given target, x, y, and dy +let trade_dtoken_for_dcash (target : nat * nat) (x : nat) (y : nat) (dy : nat) = let (a,b) = target in let target_inv = (b,a) in let current_price = price_x_to_y target_inv y x in @@ -479,11 +480,13 @@ let cash_to_token (param : cash_to_token) (storage : storage) = let cashPool = storage.cashPool in let tokens_bought = // cash -> token calculation; *includes a fee* - (bought = trade_x_for_y cashPool storage.tokenPool cashSold in - if bought < minTokensBought then + let bought = trade_dcash_for_dtoken storage.target cashPool storage.tokenPool cashSold in + let (fee_num, fee_denom) = storage.const_fee in + let bought_after_fee = bought * fee_num / fee_denom in + if bought_after_fee < minTokensBought then (failwith error_TOKENS_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_TOKENS_BOUGHT : nat) else - bought) + bought_after_fee in let new_tokenPool = (match is_nat (storage.tokenPool - tokens_bought) with | None -> (failwith error_TOKEN_POOL_MINUS_TOKENS_BOUGHT_IS_NEGATIVE : nat) @@ -523,8 +526,14 @@ let token_to_cash (param : token_to_cash) (storage : storage) = unless all liquidity has been removed. *) // token -> cash calculation; *includes a fee* let cash_bought = - let bought = trade_x_for_y storage.tokenPool storage.cashPool tokensSold in - if bought < minCashBought then (failwith error_CASH_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_CASH_BOUGHT : nat) else bought in + let bought = trade_dtoken_for_dcash storage.target storage.cashPool storage.tokenPool tokensSold in + let (fee_num, fee_denom) = storage.const_fee in + let bought_after_fee = bought * fee_num / fee_denom in + if bought_after_fee < minCashBought then + (failwith error_CASH_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_CASH_BOUGHT : nat) + else + bought_after_fee + in let op_token = token_transfer storage Tezos.sender Tezos.self_address tokensSold in #if CASH_IS_TEZ From 60a5d6274ccef2df7af9a1ea918119f041913a9e Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Mon, 9 Aug 2021 13:02:06 +0100 Subject: [PATCH 07/92] Correct price calculation --- ctez.mligo | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ctez.mligo b/ctez.mligo index a42c42b7..fd713971 100644 --- a/ctez.mligo +++ b/ctez.mligo @@ -86,7 +86,17 @@ let get_ctez_mint_or_burn (fa12_address : address) : (int * address) contract = | None -> (failwith error_CTEZ_FA12_CONTRACT_MISSING_MINT_OR_BURN_ENTRYPOINT : (int * address) contract) | Some c -> c - +// Returns the price dy/dx, i.e. a map by multiplication ∆x => ∆y, at a given point (x,y) +let price_cash_to_token (target : nat * nat) (cash : nat) (token : nat) : nat = + let (a,b) = target in + let x = cash in + let y = token in + let ax2 = x * x * a * a in + let by2 = y * y * b * b in + let num = y * (3n * ax2 + by2) in + let denom = x * (ax2 + 3n * by2) in + num/denom + (* Entrypoint Functions *) let create (s : storage) (create : create) : result = @@ -194,7 +204,7 @@ let cfmm_price (storage, tez, token : storage * nat * nat) : result = for each day over or under the target by more than 1/64th. *) - let price = (Bitwise.shift_left tez 48n) / token in + let price = price_cash_to_token (target, 1n) tez token in let target_less_price : int = target - price in let d_drift = let x = Bitwise.shift_left (abs (target_less_price * target_less_price)) 10n in From 4adca10c7be48ec9ba30877029bf7b6e9ac7ad5e Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Mon, 9 Aug 2021 13:10:56 +0100 Subject: [PATCH 08/92] Update get_target function to be compatible with the ctez_target entrypoint in the cfmm --- ctez.mligo | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ctez.mligo b/ctez.mligo index fd713971..24698707 100644 --- a/ctez.mligo +++ b/ctez.mligo @@ -27,7 +27,7 @@ type parameter = | Mint_or_burn of mint_or_burn | Cfmm_price of nat * nat | Set_addresses of set_addresses - | Get_target of nat contract + | Get_target of (nat * nat) contract type oven = {tez_balance : tez ; ctez_outstanding : nat ; address : address} @@ -181,8 +181,8 @@ let mint_or_burn (s : storage) (p : mint_or_burn) : result = let ctez_mint_or_burn = get_ctez_mint_or_burn s.ctez_fa12_address in ([Tezos.transaction (p.quantity, Tezos.sender) 0mutez ctez_mint_or_burn], s) -let get_target (storage : storage) (callback : nat contract) : result = - ([Tezos.transaction storage.target 0mutez callback], storage) +let get_target (storage : storage) (callback : (nat * nat) contract) : result = + ([Tezos.transaction (storage.target, 1n) 0mutez callback], storage) (* todo: restore when ligo interpret is fixed let cfmm_price (storage : storage) (tez : tez) (token : nat) : result = *) From 36d7943d4dc4d81b242e167dae448dd782d4e94a Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Mon, 9 Aug 2021 13:23:13 +0100 Subject: [PATCH 09/92] get_target security --- ctez.mligo | 1 + 1 file changed, 1 insertion(+) diff --git a/ctez.mligo b/ctez.mligo index 24698707..b31f403d 100644 --- a/ctez.mligo +++ b/ctez.mligo @@ -182,6 +182,7 @@ let mint_or_burn (s : storage) (p : mint_or_burn) : result = ([Tezos.transaction (p.quantity, Tezos.sender) 0mutez ctez_mint_or_burn], s) let get_target (storage : storage) (callback : (nat * nat) contract) : result = + // todo: any security on the contract itself? ([Tezos.transaction (storage.target, 1n) 0mutez callback], storage) (* todo: restore when ligo interpret is fixed From d5758a46424c53cfe50296559ff618ff5e9e2491 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Tue, 10 Aug 2021 09:01:29 +0100 Subject: [PATCH 10/92] Cleanup. --- cfmm.mligo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfmm.mligo b/cfmm.mligo index 63966e92..77bee237 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -302,7 +302,7 @@ let cash_transfer (storage : storage) (from : address) (to_ : address) (cash_amo (* In all the following calculations, cash is x, tokens are y *) // Returns the price dy/dx, i.e. a map by multiplication ∆x => ∆y, at a given point (x,y) -let price_cash_to_token (target : nat * nat) (target_b : nat) (x : nat) (y : nat) : nat = +let price_cash_to_token (target : nat * nat) (x : nat) (y : nat) : nat = let (a,b) = target in let ax2 = x * x * a * a in let by2 = y * y * b * b in From a9260f6dd08133435596cc6de3c465eef37e58c2 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Tue, 10 Aug 2021 09:56:41 +0100 Subject: [PATCH 11/92] Fix recursive notation. --- cfmm.mligo | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 77bee237..6c82b0fd 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -311,7 +311,7 @@ let price_cash_to_token (target : nat * nat) (x : nat) (y : nat) : nat = num/denom // A function to transfer assets along a curve in https://hackmd.io/MkPSYXDsTf-giBprcDrc3w -let rec newton_x_to_y (target : nat * nat) (x:nat) (y:nat) (dx:nat) (dy_approx:nat) = +let rec newton_x_to_y (target, x, y, dx, dy_approx : (nat * nat) * nat * nat * nat * nat) : nat = let (a,b) = target in let ax = a * x and by = b * y in let ax2 = ax * ax and by2 = by * by in @@ -325,13 +325,13 @@ let rec newton_x_to_y (target : nat * nat) (x:nat) (y:nat) (dx:nat) (dy_approx:n if (abs adjust) <= 1 then dy_approx - 1 (* better to be a bit stingy *) else - newton_x_to_y a b x y dx (dy - adjust) + newton_x_to_y (a, b, x, y, dx, (dy - adjust)) // A function that outputs dy (diff_token) given x, y, and dx let trade_dcash_for_dtoken (target : nat * nat) (x : nat) (y : nat) (dx : nat) = let current_price = price_x_to_y target x y in let dy_approx = current_price * dx in - newton_x_to_y target x y dx dy_approx + newton_x_to_y (target, x, y, dx, dy_approx) // A function that outputs dx (diff_cash) given target, x, y, and dy let trade_dtoken_for_dcash (target : nat * nat) (x : nat) (y : nat) (dy : nat) = @@ -339,7 +339,7 @@ let trade_dtoken_for_dcash (target : nat * nat) (x : nat) (y : nat) (dy : nat) = let target_inv = (b,a) in let current_price = price_x_to_y target_inv y x in let dx_approx = current_price * dy in - newton_x_to_y target_inv y x dy dx_approx + newton_x_to_y (target_inv, y, x, dy, dx_approx) (* ============================================================================= * Entrypoint Functions From fb0c8bb138b5361867f95fc95a08a4975ac07fd1 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Tue, 10 Aug 2021 12:37:29 +0100 Subject: [PATCH 12/92] Fix the get_target function --- ctez.mligo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctez.mligo b/ctez.mligo index b31f403d..ce12e247 100644 --- a/ctez.mligo +++ b/ctez.mligo @@ -183,7 +183,7 @@ let mint_or_burn (s : storage) (p : mint_or_burn) : result = let get_target (storage : storage) (callback : (nat * nat) contract) : result = // todo: any security on the contract itself? - ([Tezos.transaction (storage.target, 1n) 0mutez callback], storage) + ([Tezos.transaction (storage.target, (Bitwise.shift_right 2n 48n)) 0mutez callback], storage) (* todo: restore when ligo interpret is fixed let cfmm_price (storage : storage) (tez : tez) (token : nat) : result = *) From 70b7cc4dcb4f60a0bfa8623b5cfdcfff506b5495 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Tue, 10 Aug 2021 16:16:20 +0100 Subject: [PATCH 13/92] New iterative approximating function for dy given dx. --- cfmm.mligo | 54 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 6c82b0fd..316f3f14 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -300,9 +300,36 @@ let cash_transfer (storage : storage) (from : address) (to_ : address) (cash_amo #endif #endif -(* In all the following calculations, cash is x, tokens are y *) -// Returns the price dy/dx, i.e. a map by multiplication ∆x => ∆y, at a given point (x,y) -let price_cash_to_token (target : nat * nat) (x : nat) (y : nat) : nat = +(* Isoutility and Difference Equations *) +// The Isoutility Function in https://hackmd.io/MkPSYXDsTf-giBprcDrc3w +let isoutility (target, cash, token : (nat * nat) * nat * nat) : nat = + let x = cash in + let y = token in + let (a,b) = target in + let a2 = a * a in + let b2 = b * b in + let ax2 = a2 * x * x in + let by2 = b2 * y * y in + a * x * b * y * (ax2 + by2) / (2 * a2 * b2) + +// A function to transfer assets while maintaining a constant isoutility +let rec dy_given_dx (target, x, y, dx, dy_est : (nat * nat) * nat * nat * nat * nat * nat) : nat = + let (a, b) = target in + let margin_of_error = 1n in + let U_0 = (isoutility target x y) in + let U_1 = (isoutility target (x - dx) (y + dy_est)) in + if (abs(U_1 - U_0) > margin_of_error) + then + let u_ratio = U_1 / U_0 in + let adjust_scalar = (5 + 7 * u_ratio) / (7 + 5 * r) in //approximating the sixth root + let new_dy_est = adjust_scalar * dy_est in + dy_given_dx (target, x, y, dx, new_dy_est) + else + let dy = dy_est in dy + +// Returns the price dy/dx of the isoutility function, i.e. a map by multiplication ∆x => ∆y, at a given point (x,y) +let price_cash_to_token (target : nat * nat) (cash : nat) (token : nat) : nat = + let (x,y) = (cash, token) in let (a,b) = target in let ax2 = x * x * a * a in let by2 = y * y * b * b in @@ -310,28 +337,11 @@ let price_cash_to_token (target : nat * nat) (x : nat) (y : nat) : nat = let denom = x * (ax2 + 3n * by2) in num/denom -// A function to transfer assets along a curve in https://hackmd.io/MkPSYXDsTf-giBprcDrc3w -let rec newton_x_to_y (target, x, y, dx, dy_approx : (nat * nat) * nat * nat * nat * nat) : nat = - let (a,b) = target in - let ax = a * x and by = b * y in - let ax2 = ax * ax and by2 = by * by in - (* todo yp could be negative *) - let xp = x + dx and yp = y - dy_approx in - let axp = a * xp and byp = b * yp in - let num = x * y * (ax2 + by2) - xp * yp * (axp2 + byp2) - and denom = xp * (axp2 + 3n * byp2) in - let adjust = num / denom in - (* todo: check if condition always met *) - if (abs adjust) <= 1 then - dy_approx - 1 (* better to be a bit stingy *) - else - newton_x_to_y (a, b, x, y, dx, (dy - adjust)) - // A function that outputs dy (diff_token) given x, y, and dx let trade_dcash_for_dtoken (target : nat * nat) (x : nat) (y : nat) (dx : nat) = let current_price = price_x_to_y target x y in let dy_approx = current_price * dx in - newton_x_to_y (target, x, y, dx, dy_approx) + dy_given_dx (target, x, y, dx, dy_approx) // A function that outputs dx (diff_cash) given target, x, y, and dy let trade_dtoken_for_dcash (target : nat * nat) (x : nat) (y : nat) (dy : nat) = @@ -339,7 +349,7 @@ let trade_dtoken_for_dcash (target : nat * nat) (x : nat) (y : nat) (dy : nat) = let target_inv = (b,a) in let current_price = price_x_to_y target_inv y x in let dx_approx = current_price * dy in - newton_x_to_y (target_inv, y, x, dy, dx_approx) + dy_given_dx (target_inv, y, x, dy, dx_approx) (* ============================================================================= * Entrypoint Functions From d0caa68dd6302a316ba02704c1bbcf253eced75e Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Tue, 10 Aug 2021 16:18:29 +0100 Subject: [PATCH 14/92] Minor debug --- cfmm.mligo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfmm.mligo b/cfmm.mligo index 316f3f14..7d97b6ec 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -313,7 +313,7 @@ let isoutility (target, cash, token : (nat * nat) * nat * nat) : nat = a * x * b * y * (ax2 + by2) / (2 * a2 * b2) // A function to transfer assets while maintaining a constant isoutility -let rec dy_given_dx (target, x, y, dx, dy_est : (nat * nat) * nat * nat * nat * nat * nat) : nat = +let rec dy_given_dx (target, x, y, dx, dy_est : (nat * nat) * nat * nat * nat * nat ) : nat = let (a, b) = target in let margin_of_error = 1n in let U_0 = (isoutility target x y) in From 0f78d118c7cea1256e7db76adfaef986a3fa2378 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Fri, 13 Aug 2021 13:07:12 +0100 Subject: [PATCH 15/92] After experimental results showed newton descent to be more consistent, implemented newton descent with error conditions. --- cfmm.mligo | 60 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 7d97b6ec..189931e5 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -312,21 +312,6 @@ let isoutility (target, cash, token : (nat * nat) * nat * nat) : nat = let by2 = b2 * y * y in a * x * b * y * (ax2 + by2) / (2 * a2 * b2) -// A function to transfer assets while maintaining a constant isoutility -let rec dy_given_dx (target, x, y, dx, dy_est : (nat * nat) * nat * nat * nat * nat ) : nat = - let (a, b) = target in - let margin_of_error = 1n in - let U_0 = (isoutility target x y) in - let U_1 = (isoutility target (x - dx) (y + dy_est)) in - if (abs(U_1 - U_0) > margin_of_error) - then - let u_ratio = U_1 / U_0 in - let adjust_scalar = (5 + 7 * u_ratio) / (7 + 5 * r) in //approximating the sixth root - let new_dy_est = adjust_scalar * dy_est in - dy_given_dx (target, x, y, dx, new_dy_est) - else - let dy = dy_est in dy - // Returns the price dy/dx of the isoutility function, i.e. a map by multiplication ∆x => ∆y, at a given point (x,y) let price_cash_to_token (target : nat * nat) (cash : nat) (token : nat) : nat = let (x,y) = (cash, token) in @@ -337,19 +322,56 @@ let price_cash_to_token (target : nat * nat) (cash : nat) (token : nat) : nat = let denom = x * (ax2 + 3n * by2) in num/denom +// A function to transfer assets while maintaining a constant isoutility +let rec newton_x_to_y (x, y, dx, dy_approx, target : nat * nat * nat * nat * (nat * nat)) : nat = + let (a,b) = target in + let xp = x + dx in + let yp = y - dy_approx in + let ax2 = a * a * x * x and by2 = b * b * y * y in + let axp2 = a * a * xp * xp and byp2 = b * b * yp * yp in + (* Newton descent formulae *) + let num = x * y * (ax2 + by2) - xp * yp * (axp2 + byp2) in + let denom = xp * (axp2 + 3 * byp2) in + let adjust = num / denom in + if (abs adjust <= 1n) (* marginal difference calculated is <= 1mutez *) + then + let dy = dy_approx - adjust in + if y - dy <= 0 + then + (failwith error_TOKEN_POOL_MINUS_TOKENS_WITHDRAWN_IS_NEGATIVE : nat) + else + abs dy // abs to make it a nat + else + let new_dy_approx = dy_approx - adjust in + newton_x_to_y (x,y,dx,new_dy_approx,target) + (* + if denom = 0, then either: + 1. xp = 0 => x + dx = 0, which we don't allow, or + 2. a*xp = 0 and b*yp = 0 => a = 0 and (b = 0 or yp = 0), which implies + that the price target is 0. + *) + // A function that outputs dy (diff_token) given x, y, and dx -let trade_dcash_for_dtoken (target : nat * nat) (x : nat) (y : nat) (dx : nat) = +let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat * nat) : nat = let current_price = price_x_to_y target x y in let dy_approx = current_price * dx in - dy_given_dx (target, x, y, dx, dy_approx) + if (y - dy_approx <= 0) + then + (failwith error_TOKEN_POOL_MINUS_TOKENS_WITHDRAWN_IS_NEGATIVE : nat) + else + dy_given_dx (target, x, y, dx, dy_approx) // A function that outputs dx (diff_cash) given target, x, y, and dy -let trade_dtoken_for_dcash (target : nat * nat) (x : nat) (y : nat) (dy : nat) = +let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat * nat) : nat = let (a,b) = target in let target_inv = (b,a) in let current_price = price_x_to_y target_inv y x in let dx_approx = current_price * dy in - dy_given_dx (target_inv, y, x, dy, dx_approx) + if (x - dx_approx <= 0) + then + (failwith error_CASH_POOL_MINUS_CASH_WITHDRAWN_IS_NEGATIVE : nat) + else + dy_given_dx (target_inv, y, x, dy, dx_approx) (* ============================================================================= * Entrypoint Functions From 72786fc2469f40a4fb2736dc2f5458b1b3ff738b Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Fri, 13 Aug 2021 13:49:44 +0100 Subject: [PATCH 16/92] todo for division by zero --- cfmm.mligo | 1 + 1 file changed, 1 insertion(+) diff --git a/cfmm.mligo b/cfmm.mligo index 189931e5..4f7c373a 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -364,6 +364,7 @@ let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat * nat) : // A function that outputs dx (diff_cash) given target, x, y, and dy let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat * nat) : nat = let (a,b) = target in + (* todo: Problematic if target = (0,b) to begin with *) let target_inv = (b,a) in let current_price = price_x_to_y target_inv y x in let dx_approx = current_price * dy in From 13dbcfe6a7e50a2bc369187caf14705616da6737 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Fri, 13 Aug 2021 14:12:45 +0100 Subject: [PATCH 17/92] Bug fixes. --- cfmm.mligo | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 4f7c373a..6aee7079 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -310,7 +310,7 @@ let isoutility (target, cash, token : (nat * nat) * nat * nat) : nat = let b2 = b * b in let ax2 = a2 * x * x in let by2 = b2 * y * y in - a * x * b * y * (ax2 + by2) / (2 * a2 * b2) + abs (a * x * b * y * (ax2 + by2) / (2 * a2 * b2)) // Returns the price dy/dx of the isoutility function, i.e. a map by multiplication ∆x => ∆y, at a given point (x,y) let price_cash_to_token (target : nat * nat) (cash : nat) (token : nat) : nat = @@ -323,12 +323,12 @@ let price_cash_to_token (target : nat * nat) (cash : nat) (token : nat) : nat = num/denom // A function to transfer assets while maintaining a constant isoutility -let rec newton_x_to_y (x, y, dx, dy_approx, target : nat * nat * nat * nat * (nat * nat)) : nat = +let rec newton_dx_to_dy (x, y, dx, dy_approx, target : nat * nat * nat * nat * (nat * nat)) : nat = let (a,b) = target in let xp = x + dx in let yp = y - dy_approx in - let ax2 = a * a * x * x and by2 = b * b * y * y in - let axp2 = a * a * xp * xp and byp2 = b * b * yp * yp in + let ax2 = a * a * x * x in let by2 = b * b * y * y in + let axp2 = a * a * xp * xp in let byp2 = b * b * yp * yp in (* Newton descent formulae *) let num = x * y * (ax2 + by2) - xp * yp * (axp2 + byp2) in let denom = xp * (axp2 + 3 * byp2) in @@ -342,8 +342,8 @@ let rec newton_x_to_y (x, y, dx, dy_approx, target : nat * nat * nat * nat * (na else abs dy // abs to make it a nat else - let new_dy_approx = dy_approx - adjust in - newton_x_to_y (x,y,dx,new_dy_approx,target) + let new_dy_approx = abs (dy_approx - adjust) in + newton_dx_to_dy (x,y,dx,new_dy_approx,target) (* if denom = 0, then either: 1. xp = 0 => x + dx = 0, which we don't allow, or @@ -353,26 +353,26 @@ let rec newton_x_to_y (x, y, dx, dy_approx, target : nat * nat * nat * nat * (na // A function that outputs dy (diff_token) given x, y, and dx let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat * nat) : nat = - let current_price = price_x_to_y target x y in + let current_price = price_cash_to_token target x y in let dy_approx = current_price * dx in if (y - dy_approx <= 0) then (failwith error_TOKEN_POOL_MINUS_TOKENS_WITHDRAWN_IS_NEGATIVE : nat) else - dy_given_dx (target, x, y, dx, dy_approx) + newton_dx_to_dy (x, y, dx, dy_approx, target) // A function that outputs dx (diff_cash) given target, x, y, and dy let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat * nat) : nat = let (a,b) = target in (* todo: Problematic if target = (0,b) to begin with *) let target_inv = (b,a) in - let current_price = price_x_to_y target_inv y x in + let current_price = price_cash_to_token target_inv y x in let dx_approx = current_price * dy in if (x - dx_approx <= 0) then (failwith error_CASH_POOL_MINUS_CASH_WITHDRAWN_IS_NEGATIVE : nat) else - dy_given_dx (target_inv, y, x, dy, dx_approx) + newton_dx_to_dy (y, x, dy, dx_approx, target_inv) (* ============================================================================= * Entrypoint Functions @@ -513,7 +513,7 @@ let cash_to_token (param : cash_to_token) (storage : storage) = let cashPool = storage.cashPool in let tokens_bought = // cash -> token calculation; *includes a fee* - let bought = trade_dcash_for_dtoken storage.target cashPool storage.tokenPool cashSold in + let bought = trade_dcash_for_dtoken cashPool storage.tokenPool cashSold storage.target in let (fee_num, fee_denom) = storage.const_fee in let bought_after_fee = bought * fee_num / fee_denom in if bought_after_fee < minTokensBought then @@ -559,7 +559,7 @@ let token_to_cash (param : token_to_cash) (storage : storage) = unless all liquidity has been removed. *) // token -> cash calculation; *includes a fee* let cash_bought = - let bought = trade_dtoken_for_dcash storage.target storage.cashPool storage.tokenPool tokensSold in + let bought = trade_dtoken_for_dcash storage.cashPool storage.tokenPool tokensSold storage.target in let (fee_num, fee_denom) = storage.const_fee in let bought_after_fee = bought * fee_num / fee_denom in if bought_after_fee < minCashBought then From 94fff031991045516cfb18aaf7f7e7d9c87164b5 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Fri, 13 Aug 2021 15:34:09 +0100 Subject: [PATCH 18/92] Added new curve to token_to_token transfers. --- cfmm.mligo | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cfmm.mligo b/cfmm.mligo index 6aee7079..63bcbff6 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -747,7 +747,10 @@ let token_to_token (param : token_to_token) (storage : storage) : result = (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) else (* We don't check that tokenPool > 0, because that is impossible unless all liquidity has been removed. *) - let cash_bought = ((tokensSold * const_fee * storage.cashPool) / (storage.tokenPool * const_fee_denom + (tokensSold * const_fee))) in + let cash_bought = + (let bought = trade_dtoken_for_dcash storage.cashPool storage.tokenPool tokensSold storage.target in + let (fee_num, fee_denom) = storage.const_fee in + bought * fee_num / fee_denom) let new_cashPool = match is_nat (storage.cashPool - cash_bought) with | None -> (failwith error_CASH_POOL_MINUS_CASH_BOUGHT_IS_NEGATIVE : nat) | Some n -> n in From 23ef607944f393710269db2ba5d5abc668e65032 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Fri, 13 Aug 2021 15:55:07 +0100 Subject: [PATCH 19/92] Bound Newton to 4 rounds of computation. --- cfmm.mligo | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 63bcbff6..4afb4eae 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -323,7 +323,7 @@ let price_cash_to_token (target : nat * nat) (cash : nat) (token : nat) : nat = num/denom // A function to transfer assets while maintaining a constant isoutility -let rec newton_dx_to_dy (x, y, dx, dy_approx, target : nat * nat * nat * nat * (nat * nat)) : nat = +let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * nat * (nat * nat) * int) : nat = let (a,b) = target in let xp = x + dx in let yp = y - dy_approx in @@ -333,7 +333,8 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target : nat * nat * nat * nat * ( let num = x * y * (ax2 + by2) - xp * yp * (axp2 + byp2) in let denom = xp * (axp2 + 3 * byp2) in let adjust = num / denom in - if (abs adjust <= 1n) (* marginal difference calculated is <= 1mutez *) + // if (abs adjust <= 1n) (* marginal difference calculated is <= 1mutez *) + if (rounds <= 0) (* Newton converges in 4 rounds, so we bound computation there *) then let dy = dy_approx - adjust in if y - dy <= 0 @@ -343,7 +344,8 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target : nat * nat * nat * nat * ( abs dy // abs to make it a nat else let new_dy_approx = abs (dy_approx - adjust) in - newton_dx_to_dy (x,y,dx,new_dy_approx,target) + let next_round = rounds - 1 in + newton_dx_to_dy (x,y,dx,new_dy_approx,target,next_round) (* if denom = 0, then either: 1. xp = 0 => x + dx = 0, which we don't allow, or @@ -355,11 +357,12 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target : nat * nat * nat * nat * ( let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat * nat) : nat = let current_price = price_cash_to_token target x y in let dy_approx = current_price * dx in + let rounds = 4 in // Newton converges in about 4 rounds if (y - dy_approx <= 0) then (failwith error_TOKEN_POOL_MINUS_TOKENS_WITHDRAWN_IS_NEGATIVE : nat) else - newton_dx_to_dy (x, y, dx, dy_approx, target) + newton_dx_to_dy (x, y, dx, dy_approx, target, rounds) // A function that outputs dx (diff_cash) given target, x, y, and dy let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat * nat) : nat = @@ -368,11 +371,12 @@ let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat * nat) : let target_inv = (b,a) in let current_price = price_cash_to_token target_inv y x in let dx_approx = current_price * dy in + let rounds = 4 in // Newton converges in about 4 rounds if (x - dx_approx <= 0) then (failwith error_CASH_POOL_MINUS_CASH_WITHDRAWN_IS_NEGATIVE : nat) else - newton_dx_to_dy (y, x, dy, dx_approx, target_inv) + newton_dx_to_dy (y, x, dy, dx_approx, target_inv, rounds) (* ============================================================================= * Entrypoint Functions @@ -751,6 +755,7 @@ let token_to_token (param : token_to_token) (storage : storage) : result = (let bought = trade_dtoken_for_dcash storage.cashPool storage.tokenPool tokensSold storage.target in let (fee_num, fee_denom) = storage.const_fee in bought * fee_num / fee_denom) + in let new_cashPool = match is_nat (storage.cashPool - cash_bought) with | None -> (failwith error_CASH_POOL_MINUS_CASH_BOUGHT_IS_NEGATIVE : nat) | Some n -> n in From f3147d916713c00266f4c3a328a3f5b5b1d8dcd6 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Fri, 13 Aug 2021 17:48:07 +0100 Subject: [PATCH 20/92] Add capability for choosing number of rounds in difference equation calculations. --- cfmm.mligo | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 4afb4eae..4188952a 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -43,6 +43,7 @@ type cash_to_token = cashSold : nat ; (* if cash isn't tez, how much cash is sought to be sold *) #endif deadline : timestamp ; (* time before which the request must be completed *) + rounds : nat ; (* number of iterations in estimating the difference equations. Default should be 4n. *) } type token_to_cash = @@ -51,6 +52,7 @@ type token_to_cash = tokensSold : nat ; (* how many tokens are being sold *) minCashBought : nat ; (* minimum amount of cash desired *) deadline : timestamp ; (* time before which the request must be completed *) + rounds : nat ; (* number of iterations in estimating the difference equations. Default should be 4n. *) } @@ -61,6 +63,7 @@ type token_to_token = [@annot:to] to_ : address ; (* where to send the output tokens *) tokensSold : nat ; (* amount of tokens to sell *) deadline : timestamp ; (* time before which the request must be completed *) + rounds : nat ; (* number of iterations in estimating the difference equations. Default should be 4n. *) } #if HAS_BAKER @@ -323,7 +326,7 @@ let price_cash_to_token (target : nat * nat) (cash : nat) (token : nat) : nat = num/denom // A function to transfer assets while maintaining a constant isoutility -let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * nat * (nat * nat) * int) : nat = +let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * nat * (nat * nat) * nat) : nat = let (a,b) = target in let xp = x + dx in let yp = y - dy_approx in @@ -334,7 +337,7 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * let denom = xp * (axp2 + 3 * byp2) in let adjust = num / denom in // if (abs adjust <= 1n) (* marginal difference calculated is <= 1mutez *) - if (rounds <= 0) (* Newton converges in 4 rounds, so we bound computation there *) + if (rounds <= 0n) (* Newton converges in 4 rounds, so we bound computation there *) then let dy = dy_approx - adjust in if y - dy <= 0 @@ -344,7 +347,7 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * abs dy // abs to make it a nat else let new_dy_approx = abs (dy_approx - adjust) in - let next_round = rounds - 1 in + let next_round = abs (rounds - 1n) in newton_dx_to_dy (x,y,dx,new_dy_approx,target,next_round) (* if denom = 0, then either: @@ -354,10 +357,9 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * *) // A function that outputs dy (diff_token) given x, y, and dx -let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat * nat) : nat = +let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat * nat) (rounds : nat) : nat = let current_price = price_cash_to_token target x y in let dy_approx = current_price * dx in - let rounds = 4 in // Newton converges in about 4 rounds if (y - dy_approx <= 0) then (failwith error_TOKEN_POOL_MINUS_TOKENS_WITHDRAWN_IS_NEGATIVE : nat) @@ -365,13 +367,12 @@ let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat * nat) : newton_dx_to_dy (x, y, dx, dy_approx, target, rounds) // A function that outputs dx (diff_cash) given target, x, y, and dy -let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat * nat) : nat = +let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat * nat) (rounds : nat) : nat = let (a,b) = target in (* todo: Problematic if target = (0,b) to begin with *) let target_inv = (b,a) in let current_price = price_cash_to_token target_inv y x in let dx_approx = current_price * dy in - let rounds = 4 in // Newton converges in about 4 rounds if (x - dx_approx <= 0) then (failwith error_CASH_POOL_MINUS_CASH_WITHDRAWN_IS_NEGATIVE : nat) @@ -502,7 +503,8 @@ let cash_to_token (param : cash_to_token) (storage : storage) = #if !CASH_IS_TEZ cashSold = cashSold ; #endif - deadline = deadline } = param in + deadline = deadline ; + rounds = rounds } = param in #if CASH_IS_TEZ let cashSold = mutez_to_natural Tezos.amount in @@ -517,7 +519,7 @@ let cash_to_token (param : cash_to_token) (storage : storage) = let cashPool = storage.cashPool in let tokens_bought = // cash -> token calculation; *includes a fee* - let bought = trade_dcash_for_dtoken cashPool storage.tokenPool cashSold storage.target in + let bought = trade_dcash_for_dtoken cashPool storage.tokenPool cashSold storage.target rounds in let (fee_num, fee_denom) = storage.const_fee in let bought_after_fee = bought * fee_num / fee_denom in if bought_after_fee < minTokensBought then @@ -550,7 +552,8 @@ let token_to_cash (param : token_to_cash) (storage : storage) = let { to_ = to_ ; tokensSold = tokensSold ; minCashBought = minCashBought ; - deadline = deadline } = param in + deadline = deadline ; + rounds = rounds } = param in if storage.pendingPoolUpdates > 0n then (failwith error_PENDING_POOL_UPDATES_MUST_BE_ZERO : result) @@ -563,7 +566,7 @@ let token_to_cash (param : token_to_cash) (storage : storage) = unless all liquidity has been removed. *) // token -> cash calculation; *includes a fee* let cash_bought = - let bought = trade_dtoken_for_dcash storage.cashPool storage.tokenPool tokensSold storage.target in + let bought = trade_dtoken_for_dcash storage.cashPool storage.tokenPool tokensSold storage.target rounds in let (fee_num, fee_denom) = storage.const_fee in let bought_after_fee = bought * fee_num / fee_denom in if bought_after_fee < minCashBought then @@ -736,7 +739,8 @@ let token_to_token (param : token_to_token) (storage : storage) : result = minTokensBought = minTokensBought ; to_ = to_ ; tokensSold = tokensSold ; - deadline = deadline } = param in + deadline = deadline ; + rounds = rounds } = param in let outputCfmmContract_contract: cash_to_token contract = (match (Tezos.get_entrypoint_opt "%cashToToken" outputCfmmContract : cash_to_token contract option) with @@ -752,7 +756,7 @@ let token_to_token (param : token_to_token) (storage : storage) : result = else (* We don't check that tokenPool > 0, because that is impossible unless all liquidity has been removed. *) let cash_bought = - (let bought = trade_dtoken_for_dcash storage.cashPool storage.tokenPool tokensSold storage.target in + (let bought = trade_dtoken_for_dcash storage.cashPool storage.tokenPool tokensSold storage.target rounds in let (fee_num, fee_denom) = storage.const_fee in bought * fee_num / fee_denom) in @@ -764,7 +768,7 @@ let token_to_token (param : token_to_token) (storage : storage) : result = #if CASH_IS_TEZ let op_send_cash_to_output = Tezos.transaction { minTokensBought = minTokensBought ; - deadline = deadline; to_ = to_ } + deadline = deadline; to_ = to_ ; rounds = rounds} (natural_to_mutez cash_bought) outputCfmmContract_contract in #else @@ -784,7 +788,7 @@ let token_to_token (param : token_to_token) (storage : storage) : result = #endif let op_send_cash_to_output = Tezos.transaction { minTokensBought = minTokensBought ; cashSold = cash_bought ; - deadline = deadline ; to_ = to_} + deadline = deadline ; to_ = to_ ; rounds = rounds} 0mutez outputCfmmContract_contract in #endif From 99edc35c60297ce0d6ace8edf1c82a91b011813e Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Mon, 16 Aug 2021 08:43:57 +0100 Subject: [PATCH 21/92] Use option type to make optional arg for convergence of the diff equations. --- cfmm.mligo | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 4188952a..1854bf69 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -43,7 +43,7 @@ type cash_to_token = cashSold : nat ; (* if cash isn't tez, how much cash is sought to be sold *) #endif deadline : timestamp ; (* time before which the request must be completed *) - rounds : nat ; (* number of iterations in estimating the difference equations. Default should be 4n. *) + rounds : nat option ; (* number of iterations in estimating the difference equations. Default should be 4n. *) } type token_to_cash = @@ -52,7 +52,7 @@ type token_to_cash = tokensSold : nat ; (* how many tokens are being sold *) minCashBought : nat ; (* minimum amount of cash desired *) deadline : timestamp ; (* time before which the request must be completed *) - rounds : nat ; (* number of iterations in estimating the difference equations. Default should be 4n. *) + rounds : nat option ; (* number of iterations in estimating the difference equations. Default should be 4n. *) } @@ -63,7 +63,7 @@ type token_to_token = [@annot:to] to_ : address ; (* where to send the output tokens *) tokensSold : nat ; (* amount of tokens to sell *) deadline : timestamp ; (* time before which the request must be completed *) - rounds : nat ; (* number of iterations in estimating the difference equations. Default should be 4n. *) + rounds : nat option ; (* number of iterations in estimating the difference equations. Default should be 4n. *) } #if HAS_BAKER @@ -326,7 +326,7 @@ let price_cash_to_token (target : nat * nat) (cash : nat) (token : nat) : nat = num/denom // A function to transfer assets while maintaining a constant isoutility -let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * nat * (nat * nat) * nat) : nat = +let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * nat * (nat * nat) * (nat option)) : nat = let (a,b) = target in let xp = x + dx in let yp = y - dy_approx in @@ -337,7 +337,12 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * let denom = xp * (axp2 + 3 * byp2) in let adjust = num / denom in // if (abs adjust <= 1n) (* marginal difference calculated is <= 1mutez *) - if (rounds <= 0n) (* Newton converges in 4 rounds, so we bound computation there *) + let n = + (match rounds with + | None -> 4n + | Some n -> n) + in + if (n <= 0n) (* Newton converges in 4 rounds, so we bound computation there *) then let dy = dy_approx - adjust in if y - dy <= 0 @@ -347,7 +352,7 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * abs dy // abs to make it a nat else let new_dy_approx = abs (dy_approx - adjust) in - let next_round = abs (rounds - 1n) in + let next_round = Some (abs (n - 1n)) in newton_dx_to_dy (x,y,dx,new_dy_approx,target,next_round) (* if denom = 0, then either: @@ -357,7 +362,7 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * *) // A function that outputs dy (diff_token) given x, y, and dx -let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat * nat) (rounds : nat) : nat = +let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat * nat) (rounds : nat option) : nat = let current_price = price_cash_to_token target x y in let dy_approx = current_price * dx in if (y - dy_approx <= 0) @@ -367,7 +372,7 @@ let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat * nat) ( newton_dx_to_dy (x, y, dx, dy_approx, target, rounds) // A function that outputs dx (diff_cash) given target, x, y, and dy -let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat * nat) (rounds : nat) : nat = +let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat * nat) (rounds : nat option) : nat = let (a,b) = target in (* todo: Problematic if target = (0,b) to begin with *) let target_inv = (b,a) in From 6d921e0ece0021fbb6ef2f940e81ded81aceac8e Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Mon, 16 Aug 2021 09:48:01 +0100 Subject: [PATCH 22/92] Remove optional args; UX can do that. --- cfmm.mligo | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 1854bf69..4188952a 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -43,7 +43,7 @@ type cash_to_token = cashSold : nat ; (* if cash isn't tez, how much cash is sought to be sold *) #endif deadline : timestamp ; (* time before which the request must be completed *) - rounds : nat option ; (* number of iterations in estimating the difference equations. Default should be 4n. *) + rounds : nat ; (* number of iterations in estimating the difference equations. Default should be 4n. *) } type token_to_cash = @@ -52,7 +52,7 @@ type token_to_cash = tokensSold : nat ; (* how many tokens are being sold *) minCashBought : nat ; (* minimum amount of cash desired *) deadline : timestamp ; (* time before which the request must be completed *) - rounds : nat option ; (* number of iterations in estimating the difference equations. Default should be 4n. *) + rounds : nat ; (* number of iterations in estimating the difference equations. Default should be 4n. *) } @@ -63,7 +63,7 @@ type token_to_token = [@annot:to] to_ : address ; (* where to send the output tokens *) tokensSold : nat ; (* amount of tokens to sell *) deadline : timestamp ; (* time before which the request must be completed *) - rounds : nat option ; (* number of iterations in estimating the difference equations. Default should be 4n. *) + rounds : nat ; (* number of iterations in estimating the difference equations. Default should be 4n. *) } #if HAS_BAKER @@ -326,7 +326,7 @@ let price_cash_to_token (target : nat * nat) (cash : nat) (token : nat) : nat = num/denom // A function to transfer assets while maintaining a constant isoutility -let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * nat * (nat * nat) * (nat option)) : nat = +let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * nat * (nat * nat) * nat) : nat = let (a,b) = target in let xp = x + dx in let yp = y - dy_approx in @@ -337,12 +337,7 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * let denom = xp * (axp2 + 3 * byp2) in let adjust = num / denom in // if (abs adjust <= 1n) (* marginal difference calculated is <= 1mutez *) - let n = - (match rounds with - | None -> 4n - | Some n -> n) - in - if (n <= 0n) (* Newton converges in 4 rounds, so we bound computation there *) + if (rounds <= 0n) (* Newton converges in 4 rounds, so we bound computation there *) then let dy = dy_approx - adjust in if y - dy <= 0 @@ -352,7 +347,7 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * abs dy // abs to make it a nat else let new_dy_approx = abs (dy_approx - adjust) in - let next_round = Some (abs (n - 1n)) in + let next_round = abs (rounds - 1n) in newton_dx_to_dy (x,y,dx,new_dy_approx,target,next_round) (* if denom = 0, then either: @@ -362,7 +357,7 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * *) // A function that outputs dy (diff_token) given x, y, and dx -let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat * nat) (rounds : nat option) : nat = +let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat * nat) (rounds : nat) : nat = let current_price = price_cash_to_token target x y in let dy_approx = current_price * dx in if (y - dy_approx <= 0) @@ -372,7 +367,7 @@ let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat * nat) ( newton_dx_to_dy (x, y, dx, dy_approx, target, rounds) // A function that outputs dx (diff_cash) given target, x, y, and dy -let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat * nat) (rounds : nat option) : nat = +let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat * nat) (rounds : nat) : nat = let (a,b) = target in (* todo: Problematic if target = (0,b) to begin with *) let target_inv = (b,a) in From 1d3a08157d31c372f271a401c55547e667a813e1 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Tue, 17 Aug 2021 10:00:37 +0100 Subject: [PATCH 23/92] Correct target in ctez in response to comment. --- ctez.mligo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctez.mligo b/ctez.mligo index ce12e247..64d32613 100644 --- a/ctez.mligo +++ b/ctez.mligo @@ -205,7 +205,7 @@ let cfmm_price (storage, tez, token : storage * nat * nat) : result = for each day over or under the target by more than 1/64th. *) - let price = price_cash_to_token (target, 1n) tez token in + let price = price_cash_to_token (target, (Bitwise.shift_left 2n 48n)) tez token in let target_less_price : int = target - price in let d_drift = let x = Bitwise.shift_left (abs (target_less_price * target_less_price)) 10n in From 68d47d192d67efe5a107a5d43fad5120d7b35748 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Tue, 17 Aug 2021 10:23:00 +0100 Subject: [PATCH 24/92] Formulae -> formula --- cfmm.mligo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfmm.mligo b/cfmm.mligo index 4188952a..9ae7e2b0 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -332,7 +332,7 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * let yp = y - dy_approx in let ax2 = a * a * x * x in let by2 = b * b * y * y in let axp2 = a * a * xp * xp in let byp2 = b * b * yp * yp in - (* Newton descent formulae *) + (* Newton descent formula *) let num = x * y * (ax2 + by2) - xp * yp * (axp2 + byp2) in let denom = xp * (axp2 + 3 * byp2) in let adjust = num / denom in From 80aebda5be0eb89237240444e6c1b40e5cdad5fa Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Tue, 17 Aug 2021 10:55:04 +0100 Subject: [PATCH 25/92] Price variation which offloads computation to cfmm --- ctez.mligo | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/ctez.mligo b/ctez.mligo index 64d32613..5c345854 100644 --- a/ctez.mligo +++ b/ctez.mligo @@ -25,7 +25,7 @@ type parameter = | Liquidate of liquidate | Register_deposit of register_deposit | Mint_or_burn of mint_or_burn - | Cfmm_price of nat * nat + | Cfmm_price of nat | Set_addresses of set_addresses | Get_target of (nat * nat) contract @@ -86,17 +86,6 @@ let get_ctez_mint_or_burn (fa12_address : address) : (int * address) contract = | None -> (failwith error_CTEZ_FA12_CONTRACT_MISSING_MINT_OR_BURN_ENTRYPOINT : (int * address) contract) | Some c -> c -// Returns the price dy/dx, i.e. a map by multiplication ∆x => ∆y, at a given point (x,y) -let price_cash_to_token (target : nat * nat) (cash : nat) (token : nat) : nat = - let (a,b) = target in - let x = cash in - let y = token in - let ax2 = x * x * a * a in - let by2 = y * y * b * b in - let num = y * (3n * ax2 + by2) in - let denom = x * (ax2 + 3n * by2) in - num/denom - (* Entrypoint Functions *) let create (s : storage) (create : create) : result = @@ -187,7 +176,7 @@ let get_target (storage : storage) (callback : (nat * nat) contract) : result = (* todo: restore when ligo interpret is fixed let cfmm_price (storage : storage) (tez : tez) (token : nat) : result = *) -let cfmm_price (storage, tez, token : storage * nat * nat) : result = +let cfmm_price (storage, price : storage * nat) : result = if Tezos.sender <> storage.cfmm_address then (failwith error_CALLER_MUST_BE_CFMM : result) else @@ -205,7 +194,6 @@ let cfmm_price (storage, tez, token : storage * nat * nat) : result = for each day over or under the target by more than 1/64th. *) - let price = price_cash_to_token (target, (Bitwise.shift_left 2n 48n)) tez token in let target_less_price : int = target - price in let d_drift = let x = Bitwise.shift_left (abs (target_less_price * target_less_price)) 10n in @@ -227,7 +215,7 @@ let main (p, s : parameter * storage) : result = | Create d -> (create s d : result) | Liquidate l -> (liquidate s l : result) | Mint_or_burn xs -> (mint_or_burn s xs : result) - | Cfmm_price xs -> (cfmm_price (s, xs.0, xs.1) : result) + | Cfmm_price x -> (cfmm_price (s, x) : result) | Set_addresses xs -> (set_addresses s xs : result) | Get_target t -> (get_target s t : result) From 1be51638deaba7cefd14c6486f58f0564df2325f Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Tue, 17 Aug 2021 11:05:30 +0100 Subject: [PATCH 26/92] Improve Newton accuracy, keeping underestimates. --- cfmm.mligo | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 9ae7e2b0..9bc6cf17 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -335,11 +335,11 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * (* Newton descent formula *) let num = x * y * (ax2 + by2) - xp * yp * (axp2 + byp2) in let denom = xp * (axp2 + 3 * byp2) in - let adjust = num / denom in - // if (abs adjust <= 1n) (* marginal difference calculated is <= 1mutez *) + let adjust = (-num) / denom in + // if (abs adjust = 0n) (* marginal difference calculated is rounded down to 0mutez *) if (rounds <= 0n) (* Newton converges in 4 rounds, so we bound computation there *) then - let dy = dy_approx - adjust in + let dy = dy_approx + adjust in if y - dy <= 0 then (failwith error_TOKEN_POOL_MINUS_TOKENS_WITHDRAWN_IS_NEGATIVE : nat) From 0435bb4948924fc60693c096fdbdc8702ddd2c19 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Tue, 17 Aug 2021 11:07:36 +0100 Subject: [PATCH 27/92] Remove redundant check. --- cfmm.mligo | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 9bc6cf17..fc72d3db 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -340,11 +340,7 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * if (rounds <= 0n) (* Newton converges in 4 rounds, so we bound computation there *) then let dy = dy_approx + adjust in - if y - dy <= 0 - then - (failwith error_TOKEN_POOL_MINUS_TOKENS_WITHDRAWN_IS_NEGATIVE : nat) - else - abs dy // abs to make it a nat + abs dy // abs to make it a nat else let new_dy_approx = abs (dy_approx - adjust) in let next_round = abs (rounds - 1n) in From 68233b3e921d98726948829b62d59c66135f1770 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Tue, 24 Aug 2021 13:48:21 +0100 Subject: [PATCH 28/92] Adding first iteration of tests. --- test.mligo | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 test.mligo diff --git a/test.mligo b/test.mligo new file mode 100644 index 00000000..a236a637 --- /dev/null +++ b/test.mligo @@ -0,0 +1,128 @@ +(* This is a testing framework for ctez *) + +(* ============================================================================= + * Contract Templates + * ============================================================================= *) + +#include "fa12.mligo" +let main_fa12 = main +type fa12_storage = storage +type fa12_parameter = parameter +type fa12_result = result + +let main_lqt = main +type lqt_storage = storage +type lqt_parameter = parameter +type lqt_result = result + +#include "ctez.mligo" +let main_ctez = main +type ctez_storage = storage +type ctez_parameter = parameter +type ctez_result = result + +#include "cfmm.mligo" +let main_cfmm = main +type cfmm_storage = storage +type cfmm_parameter = parameter +type cfmm_result = result + + +(* ============================================================================= + * Tests + * ============================================================================= *) + +let test_setup = + // generate some implicit addresses + let reset_state_unit = Test.reset_state 5n ([] : nat list) in + let (addr_alice, addr_bob, addr_lqt, addr_dummy, addr_admin) = + (Test.nth_bootstrap_account 0, Test.nth_bootstrap_account 1, Test.nth_bootstrap_account 2, + Test.nth_bootstrap_account 3, Test.nth_bootstrap_account 4) + in + let null_address = ("tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" : address) in + + // ctez fa12 contract + let fa12_init_storage : fa12_storage = + { + tokens = ( Big_map.literal [(addr_alice, 1000n); (addr_bob, 1000n)] : (address, nat) big_map); + allowances = (Big_map.empty : allowances); + admin = addr_admin; + total_supply = 1_000_000n; + } in + let (typed_addr_fa12, program_fa12, size_fa12) = Test.originate main_fa12 fa12_init_storage 0tez in + + // lqt fa12 contract + let lqt_init_storage : fa12_storage = { + tokens = ( Big_map.literal [(addr_lqt, 100n);] : (address, nat) big_map); + allowances = (Big_map.empty : allowances); + admin = addr_admin; + total_supply = 1_000_000n; + } in + let (typed_addr_lqt, program_lqt, size_lqt) = Test.originate main_lqt lqt_init_storage 0tez in + + // ctez contract + let ctez_init_storage : ctez_storage = { + ovens = (Big_map.empty : (oven_handle, oven) big_map); + target = 103n;//(103n,100n); // TODO go up and down 5% in 0.1% increments + drift = 105;//(105,100); // TODO should this actually be a pair? + last_drift_update = ("2000-01-01t10:10:10Z" : timestamp); // 5 mins (300 sec), this should vary I think + ctez_fa12_address = null_address; + cfmm_address = null_address; + } in + let (typed_addr_ctez, program_ctez, size_ctez) = + Test.originate main_ctez ctez_init_storage 0tez + in + + // initiate the cfmm contract + let cfmm_init_storage : cfmm_storage = { + tokenPool = 10000n ; + cashPool = 10000n ; + lqtTotal = 100n ; + target = (103n,10n) ; + const_fee = (1n,100n) ; + ctez_address = Tezos.address (Test.to_contract typed_addr_ctez) ; + pendingPoolUpdates = 0n ; + tokenAddress = Tezos.address (Test.to_contract typed_addr_fa12) ; + lqtAddress = Tezos.address (Test.to_contract typed_addr_lqt) ; + } in + let (typed_addr_cfmm, program_cfmm, size_cfmm) = Test.originate main_cfmm cfmm_init_storage 0tez in + + // update ctez's storage using its set_addresses entrypoints + let ctez_entrypoint_set_addresses = + ((Test.to_entrypoint "set_addresses" typed_addr_ctez) : set_addresses contract) in + let untyped_addr_cfmm = Tezos.address (Test.to_contract typed_addr_cfmm) in + let untyped_addr_fa12 = Tezos.address (Test.to_contract typed_addr_fa12) in + let new_addresses : set_addresses = { + cfmm_address=untyped_addr_cfmm; + ctez_fa12_address=untyped_addr_fa12; + } in + let update_ctez_addresses = Test.transfer_to_contract_exn ctez_entrypoint_set_addresses new_addresses 0tez in + + // make sure ctez's storage is as expected + let implied_ctez_storage = { ctez_init_storage with cfmm_address=untyped_addr_cfmm ; ctez_fa12_address=untyped_addr_fa12 } in + let actual_ctez_storage = Test.get_storage typed_addr_ctez in + + (* DOESN'T WORK YET DUE TO BUG IN LIGO *) + Test.michelson_equal (Test.compile_value implied_ctez_storage) (Test.compile_value actual_ctez_storage) + + (* ALSO DOESN'T YET WORK + let implied_ctez_storage_ligo = { ctez_init_storage with cfmm_address=untyped_addr_cfmm ; ctez_fa12_address=untyped_addr_fa12 } in + let implied_ctez_storage = Test.compile_value implied_ctez_storage_ligo in + let actual_ctez_storage = Test.get_storage_of_address (Tezos.address (Test.to_contract typed_addr_ctez)) in + + Test.michelson_equal implied_ctez_storage actual_ctez_storage + *) + + + +(* Test that the difference equations in trades computed as expected *) +let test_diff_equations = true + + +(* Tests compilation under different directives (may not be feasible in this framework) *) +let test_directives = true + + + +(* Checks that drift and target grew at expected rate after x mins *) +let test_price = true \ No newline at end of file From 0ad5ed6ad709ed48dc154c699776774bd2f4c432 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Tue, 24 Aug 2021 13:54:02 +0100 Subject: [PATCH 29/92] moved test.mligo into the correct directory --- test.mligo => tests/test.mligo | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename test.mligo => tests/test.mligo (98%) diff --git a/test.mligo b/tests/test.mligo similarity index 98% rename from test.mligo rename to tests/test.mligo index a236a637..40284674 100644 --- a/test.mligo +++ b/tests/test.mligo @@ -4,7 +4,7 @@ * Contract Templates * ============================================================================= *) -#include "fa12.mligo" +#include "../fa12.mligo" let main_fa12 = main type fa12_storage = storage type fa12_parameter = parameter @@ -15,13 +15,13 @@ type lqt_storage = storage type lqt_parameter = parameter type lqt_result = result -#include "ctez.mligo" +#include "../ctez.mligo" let main_ctez = main type ctez_storage = storage type ctez_parameter = parameter type ctez_result = result -#include "cfmm.mligo" +#include "../cfmm.mligo" let main_cfmm = main type cfmm_storage = storage type cfmm_parameter = parameter From 91ae65144b3cd30248b3c99116a73f613e010b09 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Wed, 25 Aug 2021 09:33:27 +0100 Subject: [PATCH 30/92] temporarily move test to the root folder --- tests/test.mligo => test.mligo | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) rename tests/test.mligo => test.mligo (86%) diff --git a/tests/test.mligo b/test.mligo similarity index 86% rename from tests/test.mligo rename to test.mligo index 40284674..2314370f 100644 --- a/tests/test.mligo +++ b/test.mligo @@ -4,7 +4,7 @@ * Contract Templates * ============================================================================= *) -#include "../fa12.mligo" +#include "fa12.mligo" let main_fa12 = main type fa12_storage = storage type fa12_parameter = parameter @@ -15,13 +15,13 @@ type lqt_storage = storage type lqt_parameter = parameter type lqt_result = result -#include "../ctez.mligo" +#include "ctez.mligo" let main_ctez = main type ctez_storage = storage type ctez_parameter = parameter type ctez_result = result -#include "../cfmm.mligo" +#include "cfmm.mligo" let main_cfmm = main type cfmm_storage = storage type cfmm_parameter = parameter @@ -102,17 +102,11 @@ let test_setup = let implied_ctez_storage = { ctez_init_storage with cfmm_address=untyped_addr_cfmm ; ctez_fa12_address=untyped_addr_fa12 } in let actual_ctez_storage = Test.get_storage typed_addr_ctez in - (* DOESN'T WORK YET DUE TO BUG IN LIGO *) - Test.michelson_equal (Test.compile_value implied_ctez_storage) (Test.compile_value actual_ctez_storage) - - (* ALSO DOESN'T YET WORK - let implied_ctez_storage_ligo = { ctez_init_storage with cfmm_address=untyped_addr_cfmm ; ctez_fa12_address=untyped_addr_fa12 } in - let implied_ctez_storage = Test.compile_value implied_ctez_storage_ligo in - let actual_ctez_storage = Test.get_storage_of_address (Tezos.address (Test.to_contract typed_addr_ctez)) in - - Test.michelson_equal implied_ctez_storage actual_ctez_storage - *) - + // assertions + ( + assert (implied_ctez_storage.cfmm_address = actual_ctez_storage.cfmm_address), + assert (implied_ctez_storage.ctez_fa12_address = actual_ctez_storage.ctez_fa12_address) + ) (* Test that the difference equations in trades computed as expected *) @@ -123,6 +117,5 @@ let test_diff_equations = true let test_directives = true - (* Checks that drift and target grew at expected rate after x mins *) let test_price = true \ No newline at end of file From b8652fc5684f681a913270117025518b1213d202 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Wed, 25 Aug 2021 11:20:26 +0100 Subject: [PATCH 31/92] Generic setup function which allows for mutation testing and a diverse range of tests. --- test.mligo | 93 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 72 insertions(+), 21 deletions(-) diff --git a/test.mligo b/test.mligo index 2314370f..e48c72d5 100644 --- a/test.mligo +++ b/test.mligo @@ -29,10 +29,27 @@ type cfmm_result = result (* ============================================================================= - * Tests + * General Setup that Initiates All Contracts * ============================================================================= *) -let test_setup = +let init_contracts (alice_bal : nat option) (bob_bal : nat option) (init_lqt : nat option) (init_total_supply : nat option) + (init_token_pool : nat option) (init_cash_pool : nat option) (init_target : (nat * nat) option) + (init_drift : (int * int) option) (last_drift_update : timestamp option) (const_fee : (nat * nat) option) + (pending_pool_updates : nat option) (init_ovens : (oven_handle, oven) big_map option) = + // set defaults for optional args + let alice_bal = match alice_bal with | None -> 100n | Some b -> b in + let bob_bal = match bob_bal with | None -> 100n | Some b -> b in + let init_lqt = match init_lqt with | None -> 10n | Some l -> l in + let init_total_supply = match init_total_supply with | None -> 1_000_000n | Some s -> s in + let init_token_pool = match init_token_pool with | None -> 10_000n | Some t -> t in + let init_cash_pool = match init_cash_pool with | None -> 10_000n | Some c -> c in + let init_target = match init_target with | None -> (103n,100n) | Some t -> t in + let init_drift = match init_drift with | None -> (105,100) | Some d -> d in + let last_drift_update = match last_drift_update with | None -> ("2021-01-01t10:10:10Z" : timestamp) | Some t -> t in + let const_fee = match const_fee with | None -> (1n, 100n) | Some f -> f in + let pending_pool_updates = match pending_pool_updates with | None -> 0n | Some p -> p in + let init_ovens = match init_ovens with | None -> (Big_map.empty : (oven_handle, oven) big_map) | Some o -> o in + // generate some implicit addresses let reset_state_unit = Test.reset_state 5n ([] : nat list) in let (addr_alice, addr_bob, addr_lqt, addr_dummy, addr_admin) = @@ -44,28 +61,28 @@ let test_setup = // ctez fa12 contract let fa12_init_storage : fa12_storage = { - tokens = ( Big_map.literal [(addr_alice, 1000n); (addr_bob, 1000n)] : (address, nat) big_map); + tokens = ( Big_map.literal [(addr_alice, alice_bal); (addr_bob, bob_bal)] : (address, nat) big_map); allowances = (Big_map.empty : allowances); admin = addr_admin; - total_supply = 1_000_000n; + total_supply = init_total_supply; } in let (typed_addr_fa12, program_fa12, size_fa12) = Test.originate main_fa12 fa12_init_storage 0tez in // lqt fa12 contract let lqt_init_storage : fa12_storage = { - tokens = ( Big_map.literal [(addr_lqt, 100n);] : (address, nat) big_map); + tokens = ( Big_map.literal [(addr_lqt, init_lqt);] : (address, nat) big_map); allowances = (Big_map.empty : allowances); admin = addr_admin; - total_supply = 1_000_000n; + total_supply = init_total_supply; } in let (typed_addr_lqt, program_lqt, size_lqt) = Test.originate main_lqt lqt_init_storage 0tez in // ctez contract let ctez_init_storage : ctez_storage = { - ovens = (Big_map.empty : (oven_handle, oven) big_map); - target = 103n;//(103n,100n); // TODO go up and down 5% in 0.1% increments + ovens = init_ovens; + target = 103n;//(103n,100n); drift = 105;//(105,100); // TODO should this actually be a pair? - last_drift_update = ("2000-01-01t10:10:10Z" : timestamp); // 5 mins (300 sec), this should vary I think + last_drift_update = last_drift_update; ctez_fa12_address = null_address; cfmm_address = null_address; } in @@ -75,11 +92,11 @@ let test_setup = // initiate the cfmm contract let cfmm_init_storage : cfmm_storage = { - tokenPool = 10000n ; - cashPool = 10000n ; - lqtTotal = 100n ; - target = (103n,10n) ; - const_fee = (1n,100n) ; + tokenPool = init_token_pool ; + cashPool = init_cash_pool ; + lqtTotal = init_lqt ; + target = init_target ; + const_fee = const_fee; ctez_address = Tezos.address (Test.to_contract typed_addr_ctez) ; pendingPoolUpdates = 0n ; tokenAddress = Tezos.address (Test.to_contract typed_addr_fa12) ; @@ -98,24 +115,58 @@ let test_setup = } in let update_ctez_addresses = Test.transfer_to_contract_exn ctez_entrypoint_set_addresses new_addresses 0tez in + (typed_addr_cfmm, typed_addr_ctez, typed_addr_fa12, typed_addr_lqt) + +(* ============================================================================= + * Tests + * ============================================================================= *) + +(* Run setup with default args; verify ctez storage is as expected *) +let test_setup = + let (typed_addr_cfmm, typed_addr_ctez, typed_addr_fa12, typed_addr_lqt) = + init_contracts + (None : nat option) (* alice_bal *) + (None : nat option) (* bob_bal *) + (None : nat option) (* init_lqt *) + (None : nat option) (* init_total_supply *) + (None : nat option) (* init_token_pool *) + (None : nat option) (* init_cash_pool *) + (None : (nat * nat) option) (* init_target *) + (None : (int * int) option) (* init_drift *) + (None : timestamp option) (* last_drift_update *) + (None : (nat * nat) option) (* const_fee *) + (None : nat option) (* pending_pool_updates *) + (None : (oven_handle, oven) big_map option) (* init_ovens *) + in // make sure ctez's storage is as expected - let implied_ctez_storage = { ctez_init_storage with cfmm_address=untyped_addr_cfmm ; ctez_fa12_address=untyped_addr_fa12 } in + let untyped_addr_fa12 = Tezos.address (Test.to_contract typed_addr_fa12) in + let untyped_addr_cfmm = Tezos.address (Test.to_contract typed_addr_cfmm) in + let expected_ctez_storage : ctez_storage = { + ovens = (Big_map.empty : (oven_handle, oven) big_map); + target = 103n;//(103n,100n); // TODO go up and down 5% in 0.1% increments + drift = 105;//(105,100); // TODO should this actually be a pair? + last_drift_update = ("2000-01-01t10:10:10Z" : timestamp); // 5 mins (300 sec), this should vary I think + ctez_fa12_address = untyped_addr_fa12; + cfmm_address = untyped_addr_cfmm; + } in let actual_ctez_storage = Test.get_storage typed_addr_ctez in - // assertions + // assertions to verify storage is as expected ( - assert (implied_ctez_storage.cfmm_address = actual_ctez_storage.cfmm_address), - assert (implied_ctez_storage.ctez_fa12_address = actual_ctez_storage.ctez_fa12_address) + assert (expected_ctez_storage.cfmm_address = actual_ctez_storage.cfmm_address), + assert (expected_ctez_storage.ctez_fa12_address = actual_ctez_storage.ctez_fa12_address) ) (* Test that the difference equations in trades computed as expected *) -let test_diff_equations = true +let test_diff_equations = () (* Tests compilation under different directives (may not be feasible in this framework) *) -let test_directives = true +let test_directives = () (* Checks that drift and target grew at expected rate after x mins *) -let test_price = true \ No newline at end of file +let test_price = () + // 5 mins, or 300 secs will be default + // target should go up and down 5% in 0.1% increments \ No newline at end of file From 01bbfe54e26e7069ffe65153bcfb076cd7307a22 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Wed, 25 Aug 2021 12:04:32 +0100 Subject: [PATCH 32/92] Changed target to nat, instead of nat * nat, in keeping with old convention. --- cfmm.mligo | 26 +++++++++++++++----------- ctez.mligo | 6 +++--- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index fc72d3db..20b0bca2 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -75,7 +75,7 @@ type set_baker = #endif (* (a,b) such that a/b is the target price from the ctez contract *) -type ctez_target = nat * nat +type ctez_target = nat (* getbalance update types for fa12 and fa2 *) type update_fa12_pool = nat @@ -305,10 +305,11 @@ let cash_transfer (storage : storage) (from : address) (to_ : address) (cash_amo (* Isoutility and Difference Equations *) // The Isoutility Function in https://hackmd.io/MkPSYXDsTf-giBprcDrc3w -let isoutility (target, cash, token : (nat * nat) * nat * nat) : nat = +let isoutility (target, cash, token : nat * nat * nat) : nat = let x = cash in let y = token in - let (a,b) = target in + let a = target in + let b = (Bitwise.shift_right 2n 48n) in // target is implicitly divided by 2 ** 48 let a2 = a * a in let b2 = b * b in let ax2 = a2 * x * x in @@ -316,9 +317,10 @@ let isoutility (target, cash, token : (nat * nat) * nat * nat) : nat = abs (a * x * b * y * (ax2 + by2) / (2 * a2 * b2)) // Returns the price dy/dx of the isoutility function, i.e. a map by multiplication ∆x => ∆y, at a given point (x,y) -let price_cash_to_token (target : nat * nat) (cash : nat) (token : nat) : nat = +let price_cash_to_token (target : nat) (cash : nat) (token : nat) : nat = let (x,y) = (cash, token) in - let (a,b) = target in + let a = target in + let b = (Bitwise.shift_right 2n 48n) in // target is implicitly divided by 2 ** 48 let ax2 = x * x * a * a in let by2 = y * y * b * b in let num = y * (3n * ax2 + by2) in @@ -326,8 +328,9 @@ let price_cash_to_token (target : nat * nat) (cash : nat) (token : nat) : nat = num/denom // A function to transfer assets while maintaining a constant isoutility -let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * nat * (nat * nat) * nat) : nat = - let (a,b) = target in +let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * nat * nat * nat) : nat = + let a = target in + let b = (Bitwise.shift_right 2n 48n) in // target is implicitly divided by 2 ** 48 let xp = x + dx in let yp = y - dy_approx in let ax2 = a * a * x * x in let by2 = b * b * y * y in @@ -353,7 +356,7 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * *) // A function that outputs dy (diff_token) given x, y, and dx -let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat * nat) (rounds : nat) : nat = +let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat) (rounds : nat) : nat = let current_price = price_cash_to_token target x y in let dy_approx = current_price * dx in if (y - dy_approx <= 0) @@ -363,10 +366,11 @@ let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat * nat) ( newton_dx_to_dy (x, y, dx, dy_approx, target, rounds) // A function that outputs dx (diff_cash) given target, x, y, and dy -let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat * nat) (rounds : nat) : nat = - let (a,b) = target in +let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat) (rounds : nat) : nat = + let a = target in + let b = (Bitwise.shift_right 2n 48n) in // target is implicitly divided by 2 ** 48 (* todo: Problematic if target = (0,b) to begin with *) - let target_inv = (b,a) in + let target_inv = b * b / a in // when later divided by b, target_inv will be b / a let current_price = price_cash_to_token target_inv y x in let dx_approx = current_price * dy in if (x - dx_approx <= 0) diff --git a/ctez.mligo b/ctez.mligo index 5c345854..4f68225a 100644 --- a/ctez.mligo +++ b/ctez.mligo @@ -27,7 +27,7 @@ type parameter = | Mint_or_burn of mint_or_burn | Cfmm_price of nat | Set_addresses of set_addresses - | Get_target of (nat * nat) contract + | Get_target of nat contract type oven = {tez_balance : tez ; ctez_outstanding : nat ; address : address} @@ -170,9 +170,9 @@ let mint_or_burn (s : storage) (p : mint_or_burn) : result = let ctez_mint_or_burn = get_ctez_mint_or_burn s.ctez_fa12_address in ([Tezos.transaction (p.quantity, Tezos.sender) 0mutez ctez_mint_or_burn], s) -let get_target (storage : storage) (callback : (nat * nat) contract) : result = +let get_target (storage : storage) (callback : nat contract) : result = // todo: any security on the contract itself? - ([Tezos.transaction (storage.target, (Bitwise.shift_right 2n 48n)) 0mutez callback], storage) + ([Tezos.transaction storage.target 0mutez callback], storage) (* todo: restore when ligo interpret is fixed let cfmm_price (storage : storage) (tez : tez) (token : nat) : result = *) From cf7007f6db0729a8d9ca4167942e3d094cd489ca Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Wed, 25 Aug 2021 12:10:50 +0100 Subject: [PATCH 33/92] Modify test so that target is a nat. --- test.mligo | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test.mligo b/test.mligo index e48c72d5..be27e993 100644 --- a/test.mligo +++ b/test.mligo @@ -29,12 +29,12 @@ type cfmm_result = result (* ============================================================================= - * General Setup that Initiates All Contracts + * Generic Setup that Initiates All Contracts * ============================================================================= *) let init_contracts (alice_bal : nat option) (bob_bal : nat option) (init_lqt : nat option) (init_total_supply : nat option) - (init_token_pool : nat option) (init_cash_pool : nat option) (init_target : (nat * nat) option) - (init_drift : (int * int) option) (last_drift_update : timestamp option) (const_fee : (nat * nat) option) + (init_token_pool : nat option) (init_cash_pool : nat option) (init_target : nat option) + (init_drift : int option) (last_drift_update : timestamp option) (const_fee : (nat * nat) option) (pending_pool_updates : nat option) (init_ovens : (oven_handle, oven) big_map option) = // set defaults for optional args let alice_bal = match alice_bal with | None -> 100n | Some b -> b in @@ -43,8 +43,8 @@ let init_contracts (alice_bal : nat option) (bob_bal : nat option) (init_lqt : n let init_total_supply = match init_total_supply with | None -> 1_000_000n | Some s -> s in let init_token_pool = match init_token_pool with | None -> 10_000n | Some t -> t in let init_cash_pool = match init_cash_pool with | None -> 10_000n | Some c -> c in - let init_target = match init_target with | None -> (103n,100n) | Some t -> t in - let init_drift = match init_drift with | None -> (105,100) | Some d -> d in + let init_target = match init_target with | None -> 103n | Some t -> t in // TODO: this init value is off + let init_drift = match init_drift with | None -> 105 | Some d -> d in // TODO: this init value is off let last_drift_update = match last_drift_update with | None -> ("2021-01-01t10:10:10Z" : timestamp) | Some t -> t in let const_fee = match const_fee with | None -> (1n, 100n) | Some f -> f in let pending_pool_updates = match pending_pool_updates with | None -> 0n | Some p -> p in @@ -81,7 +81,7 @@ let init_contracts (alice_bal : nat option) (bob_bal : nat option) (init_lqt : n let ctez_init_storage : ctez_storage = { ovens = init_ovens; target = 103n;//(103n,100n); - drift = 105;//(105,100); // TODO should this actually be a pair? + drift = init_drift; last_drift_update = last_drift_update; ctez_fa12_address = null_address; cfmm_address = null_address; @@ -131,8 +131,8 @@ let test_setup = (None : nat option) (* init_total_supply *) (None : nat option) (* init_token_pool *) (None : nat option) (* init_cash_pool *) - (None : (nat * nat) option) (* init_target *) - (None : (int * int) option) (* init_drift *) + (None : nat option) (* init_target *) + (None : int option) (* init_drift *) (None : timestamp option) (* last_drift_update *) (None : (nat * nat) option) (* const_fee *) (None : nat option) (* pending_pool_updates *) From 244e8bc969dc23b39cb0f968fe35c431dde0adb7 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Thu, 26 Aug 2021 11:20:46 +0100 Subject: [PATCH 34/92] dy_approx starts at 0n --- cfmm.mligo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 20b0bca2..1adfddbe 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -358,7 +358,7 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * // A function that outputs dy (diff_token) given x, y, and dx let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat) (rounds : nat) : nat = let current_price = price_cash_to_token target x y in - let dy_approx = current_price * dx in + let dy_approx = 0n in // start at 0n to always be an underestimate if (y - dy_approx <= 0) then (failwith error_TOKEN_POOL_MINUS_TOKENS_WITHDRAWN_IS_NEGATIVE : nat) @@ -372,7 +372,7 @@ let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat) (rounds (* todo: Problematic if target = (0,b) to begin with *) let target_inv = b * b / a in // when later divided by b, target_inv will be b / a let current_price = price_cash_to_token target_inv y x in - let dx_approx = current_price * dy in + let dx_approx = 0n in // start at 0n to always be an underestimate if (x - dx_approx <= 0) then (failwith error_CASH_POOL_MINUS_CASH_WITHDRAWN_IS_NEGATIVE : nat) From d71d81206f94ff849cf30157addf757213ed1a73 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Thu, 26 Aug 2021 11:27:25 +0100 Subject: [PATCH 35/92] Change to int --- cfmm.mligo | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 1adfddbe..b6fc14b2 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -43,7 +43,7 @@ type cash_to_token = cashSold : nat ; (* if cash isn't tez, how much cash is sought to be sold *) #endif deadline : timestamp ; (* time before which the request must be completed *) - rounds : nat ; (* number of iterations in estimating the difference equations. Default should be 4n. *) + rounds : int ; (* number of iterations in estimating the difference equations. Default should be 4n. *) } type token_to_cash = @@ -52,7 +52,7 @@ type token_to_cash = tokensSold : nat ; (* how many tokens are being sold *) minCashBought : nat ; (* minimum amount of cash desired *) deadline : timestamp ; (* time before which the request must be completed *) - rounds : nat ; (* number of iterations in estimating the difference equations. Default should be 4n. *) + rounds : int ; (* number of iterations in estimating the difference equations. Default should be 4n. *) } @@ -63,7 +63,7 @@ type token_to_token = [@annot:to] to_ : address ; (* where to send the output tokens *) tokensSold : nat ; (* amount of tokens to sell *) deadline : timestamp ; (* time before which the request must be completed *) - rounds : nat ; (* number of iterations in estimating the difference equations. Default should be 4n. *) + rounds : int ; (* number of iterations in estimating the difference equations. Default should be 4n. *) } #if HAS_BAKER @@ -328,7 +328,7 @@ let price_cash_to_token (target : nat) (cash : nat) (token : nat) : nat = num/denom // A function to transfer assets while maintaining a constant isoutility -let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * nat * nat * nat) : nat = +let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * nat * nat * int) : nat = let a = target in let b = (Bitwise.shift_right 2n 48n) in // target is implicitly divided by 2 ** 48 let xp = x + dx in @@ -340,13 +340,13 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * let denom = xp * (axp2 + 3 * byp2) in let adjust = (-num) / denom in // if (abs adjust = 0n) (* marginal difference calculated is rounded down to 0mutez *) - if (rounds <= 0n) (* Newton converges in 4 rounds, so we bound computation there *) + if (rounds <= 0) (* Newton converges in 4 rounds, so we bound computation there *) then let dy = dy_approx + adjust in abs dy // abs to make it a nat else let new_dy_approx = abs (dy_approx - adjust) in - let next_round = abs (rounds - 1n) in + let next_round = (rounds - 1) in newton_dx_to_dy (x,y,dx,new_dy_approx,target,next_round) (* if denom = 0, then either: @@ -356,7 +356,7 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * *) // A function that outputs dy (diff_token) given x, y, and dx -let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat) (rounds : nat) : nat = +let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat) (rounds : int) : nat = let current_price = price_cash_to_token target x y in let dy_approx = 0n in // start at 0n to always be an underestimate if (y - dy_approx <= 0) @@ -366,10 +366,10 @@ let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat) (rounds newton_dx_to_dy (x, y, dx, dy_approx, target, rounds) // A function that outputs dx (diff_cash) given target, x, y, and dy -let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat) (rounds : nat) : nat = +let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat) (rounds : int) : nat = let a = target in let b = (Bitwise.shift_right 2n 48n) in // target is implicitly divided by 2 ** 48 - (* todo: Problematic if target = (0,b) to begin with *) + (* Will get error if a = 0 to begin with, but if that's the case we have bigger fish to fry *) let target_inv = b * b / a in // when later divided by b, target_inv will be b / a let current_price = price_cash_to_token target_inv y x in let dx_approx = 0n in // start at 0n to always be an underestimate From 456755c7bc86982783f87301b555a8c667ed9ea3 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Thu, 26 Aug 2021 11:32:01 +0100 Subject: [PATCH 36/92] adjust is positive, rounded down, via a double negative. --- cfmm.mligo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index b6fc14b2..1bf773ce 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -1,5 +1,5 @@ (* Pick one of CASH_IS_TEZ, CASH_IS_FA2, CASH_IS_FA12. tokenToToken isn't supported for CASH_IS_FA12 *) -//#define CASH_IS_TEZ +#define CASH_IS_TEZ //#define CASH_IS_FA2 //#define CASH_IS_FA12 @@ -338,7 +338,7 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * (* Newton descent formula *) let num = x * y * (ax2 + by2) - xp * yp * (axp2 + byp2) in let denom = xp * (axp2 + 3 * byp2) in - let adjust = (-num) / denom in + let adjust = -((-num) / denom) in // double negative so the division rounds down, instead of up // if (abs adjust = 0n) (* marginal difference calculated is rounded down to 0mutez *) if (rounds <= 0) (* Newton converges in 4 rounds, so we bound computation there *) then From aeaf2e2c0cfd4518f486092331b70cf9c777fe16 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Thu, 26 Aug 2021 11:34:22 +0100 Subject: [PATCH 37/92] Deleting dead code, clarifying comments --- cfmm.mligo | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 1bf773ce..b1c9d80a 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -339,8 +339,7 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * let num = x * y * (ax2 + by2) - xp * yp * (axp2 + byp2) in let denom = xp * (axp2 + 3 * byp2) in let adjust = -((-num) / denom) in // double negative so the division rounds down, instead of up - // if (abs adjust = 0n) (* marginal difference calculated is rounded down to 0mutez *) - if (rounds <= 0) (* Newton converges in 4 rounds, so we bound computation there *) + if (rounds <= 0) (* Newton generally converges in 4 rounds, so we bound computation there *) then let dy = dy_approx + adjust in abs dy // abs to make it a nat From 91146a2709a872254826246d280eb7bb12ee7b25 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Thu, 26 Aug 2021 11:40:33 +0100 Subject: [PATCH 38/92] Optimise newton in response to Arthur's comment. --- cfmm.mligo | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index b1c9d80a..dd30829f 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -1,5 +1,5 @@ (* Pick one of CASH_IS_TEZ, CASH_IS_FA2, CASH_IS_FA12. tokenToToken isn't supported for CASH_IS_FA12 *) -#define CASH_IS_TEZ +//#define CASH_IS_TEZ //#define CASH_IS_FA2 //#define CASH_IS_FA12 @@ -329,21 +329,20 @@ let price_cash_to_token (target : nat) (cash : nat) (token : nat) : nat = // A function to transfer assets while maintaining a constant isoutility let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * nat * nat * int) : nat = - let a = target in - let b = (Bitwise.shift_right 2n 48n) in // target is implicitly divided by 2 ** 48 - let xp = x + dx in - let yp = y - dy_approx in - let ax2 = a * a * x * x in let by2 = b * b * y * y in - let axp2 = a * a * xp * xp in let byp2 = b * b * yp * yp in - (* Newton descent formula *) - let num = x * y * (ax2 + by2) - xp * yp * (axp2 + byp2) in - let denom = xp * (axp2 + 3 * byp2) in - let adjust = -((-num) / denom) in // double negative so the division rounds down, instead of up if (rounds <= 0) (* Newton generally converges in 4 rounds, so we bound computation there *) then - let dy = dy_approx + adjust in - abs dy // abs to make it a nat + dy_approx else + let a = target in + let b = (Bitwise.shift_right 2n 48n) in // target is implicitly divided by 2 ** 48 + let xp = x + dx in + let yp = y - dy_approx in + let ax2 = a * a * x * x in let by2 = b * b * y * y in + let axp2 = a * a * xp * xp in let byp2 = b * b * yp * yp in + (* Newton descent formula *) + let num = x * y * (ax2 + by2) - xp * yp * (axp2 + byp2) in + let denom = xp * (axp2 + 3 * byp2) in + let adjust = -((-num) / denom) in // double negative so the division rounds down, instead of up let new_dy_approx = abs (dy_approx - adjust) in let next_round = (rounds - 1) in newton_dx_to_dy (x,y,dx,new_dy_approx,target,next_round) From 82e46e75ba12695e1f9c7836e9834fe0158d0bea Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Thu, 26 Aug 2021 11:59:16 +0100 Subject: [PATCH 39/92] Change multiplication/division by b to bitwise shifts --- cfmm.mligo | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index dd30829f..36f197a1 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -305,24 +305,22 @@ let cash_transfer (storage : storage) (from : address) (to_ : address) (cash_amo (* Isoutility and Difference Equations *) // The Isoutility Function in https://hackmd.io/MkPSYXDsTf-giBprcDrc3w +// b is 2^48 below, implemented as bitwise shifts let isoutility (target, cash, token : nat * nat * nat) : nat = let x = cash in let y = token in let a = target in - let b = (Bitwise.shift_right 2n 48n) in // target is implicitly divided by 2 ** 48 let a2 = a * a in - let b2 = b * b in let ax2 = a2 * x * x in - let by2 = b2 * y * y in - abs (a * x * b * y * (ax2 + by2) / (2 * a2 * b2)) + let by2 = Bitwise.shift_right (y * y) 96n in + abs (Bitwise.shift_left ((a * x * y) * (ax2 + by2) / (2 * a2)) 48n) // Returns the price dy/dx of the isoutility function, i.e. a map by multiplication ∆x => ∆y, at a given point (x,y) let price_cash_to_token (target : nat) (cash : nat) (token : nat) : nat = let (x,y) = (cash, token) in let a = target in - let b = (Bitwise.shift_right 2n 48n) in // target is implicitly divided by 2 ** 48 let ax2 = x * x * a * a in - let by2 = y * y * b * b in + let by2 = Bitwise.shift_right (y * y) 96n in let num = y * (3n * ax2 + by2) in let denom = x * (ax2 + 3n * by2) in num/denom @@ -334,11 +332,11 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * dy_approx else let a = target in - let b = (Bitwise.shift_right 2n 48n) in // target is implicitly divided by 2 ** 48 + // let b = (Bitwise.shift_right 2n 48n) in // target is implicitly divided by 2 ** 48 let xp = x + dx in let yp = y - dy_approx in - let ax2 = a * a * x * x in let by2 = b * b * y * y in - let axp2 = a * a * xp * xp in let byp2 = b * b * yp * yp in + let ax2 = a * a * x * x in let by2 = Bitwise.shift_right (y * y) 96n in + let axp2 = a * a * xp * xp in let byp2 = Bitwise.shift_right (yp * yp) 96n in (* Newton descent formula *) let num = x * y * (ax2 + by2) - xp * yp * (axp2 + byp2) in let denom = xp * (axp2 + 3 * byp2) in From 238f72d909897e939c85291d392975924f68152e Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Thu, 26 Aug 2021 12:05:16 +0100 Subject: [PATCH 40/92] Some bug fixes to compile correctly. --- cfmm.mligo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 36f197a1..31d0f274 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -313,7 +313,7 @@ let isoutility (target, cash, token : nat * nat * nat) : nat = let a2 = a * a in let ax2 = a2 * x * x in let by2 = Bitwise.shift_right (y * y) 96n in - abs (Bitwise.shift_left ((a * x * y) * (ax2 + by2) / (2 * a2)) 48n) + (Bitwise.shift_left (abs((a * x * y) * (ax2 + by2) / (2 * a2))) 48n) // Returns the price dy/dx of the isoutility function, i.e. a map by multiplication ∆x => ∆y, at a given point (x,y) let price_cash_to_token (target : nat) (cash : nat) (token : nat) : nat = @@ -336,7 +336,7 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * let xp = x + dx in let yp = y - dy_approx in let ax2 = a * a * x * x in let by2 = Bitwise.shift_right (y * y) 96n in - let axp2 = a * a * xp * xp in let byp2 = Bitwise.shift_right (yp * yp) 96n in + let axp2 = a * a * xp * xp in let byp2 = Bitwise.shift_right (abs(yp * yp)) 96n in (* Newton descent formula *) let num = x * y * (ax2 + by2) - xp * yp * (axp2 + byp2) in let denom = xp * (axp2 + 3 * byp2) in From fde3ccf4cdf9e4587f09922530471b4c1231c0f0 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Thu, 26 Aug 2021 13:07:45 +0100 Subject: [PATCH 41/92] Confused left shift with right shift --- cfmm.mligo | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 31d0f274..35a29560 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -312,15 +312,15 @@ let isoutility (target, cash, token : nat * nat * nat) : nat = let a = target in let a2 = a * a in let ax2 = a2 * x * x in - let by2 = Bitwise.shift_right (y * y) 96n in - (Bitwise.shift_left (abs((a * x * y) * (ax2 + by2) / (2 * a2))) 48n) + let by2 = Bitwise.shift_left (y * y) 96n in + (Bitwise.shift_right (abs((a * x * y) * (ax2 + by2) / (2 * a2))) 48n) // Returns the price dy/dx of the isoutility function, i.e. a map by multiplication ∆x => ∆y, at a given point (x,y) let price_cash_to_token (target : nat) (cash : nat) (token : nat) : nat = let (x,y) = (cash, token) in let a = target in let ax2 = x * x * a * a in - let by2 = Bitwise.shift_right (y * y) 96n in + let by2 = Bitwise.shift_left (y * y) 96n in let num = y * (3n * ax2 + by2) in let denom = x * (ax2 + 3n * by2) in num/denom @@ -332,16 +332,15 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * dy_approx else let a = target in - // let b = (Bitwise.shift_right 2n 48n) in // target is implicitly divided by 2 ** 48 let xp = x + dx in let yp = y - dy_approx in - let ax2 = a * a * x * x in let by2 = Bitwise.shift_right (y * y) 96n in - let axp2 = a * a * xp * xp in let byp2 = Bitwise.shift_right (abs(yp * yp)) 96n in + let ax2 = a * a * x * x in let by2 = Bitwise.shift_left (y * y) 96n in + let axp2 = a * a * xp * xp in let byp2 = Bitwise.shift_left (abs(yp * yp)) 96n in (* Newton descent formula *) let num = x * y * (ax2 + by2) - xp * yp * (axp2 + byp2) in let denom = xp * (axp2 + 3 * byp2) in let adjust = -((-num) / denom) in // double negative so the division rounds down, instead of up - let new_dy_approx = abs (dy_approx - adjust) in + let new_dy_approx = abs (dy_approx + adjust) in let next_round = (rounds - 1) in newton_dx_to_dy (x,y,dx,new_dy_approx,target,next_round) (* @@ -364,9 +363,9 @@ let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat) (rounds // A function that outputs dx (diff_cash) given target, x, y, and dy let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat) (rounds : int) : nat = let a = target in - let b = (Bitwise.shift_right 2n 48n) in // target is implicitly divided by 2 ** 48 + let b2 = (Bitwise.shift_left 2n 96n) in // target is implicitly divided by 2 ** 48 (* Will get error if a = 0 to begin with, but if that's the case we have bigger fish to fry *) - let target_inv = b * b / a in // when later divided by b, target_inv will be b / a + let target_inv = b2 / a in // when later divided by b, target_inv will be b / a let current_price = price_cash_to_token target_inv y x in let dx_approx = 0n in // start at 0n to always be an underestimate if (x - dx_approx <= 0) From fb4b80882586db9f8edeb08de605366d50255d5c Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Thu, 26 Aug 2021 13:17:53 +0100 Subject: [PATCH 42/92] Remove unused vars --- cfmm.mligo | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 35a29560..dc68cf09 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -352,7 +352,6 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * // A function that outputs dy (diff_token) given x, y, and dx let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat) (rounds : int) : nat = - let current_price = price_cash_to_token target x y in let dy_approx = 0n in // start at 0n to always be an underestimate if (y - dy_approx <= 0) then @@ -363,10 +362,9 @@ let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat) (rounds // A function that outputs dx (diff_cash) given target, x, y, and dy let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat) (rounds : int) : nat = let a = target in - let b2 = (Bitwise.shift_left 2n 96n) in // target is implicitly divided by 2 ** 48 + let b2 = (Bitwise.shift_left 2n 96n) in (* Will get error if a = 0 to begin with, but if that's the case we have bigger fish to fry *) let target_inv = b2 / a in // when later divided by b, target_inv will be b / a - let current_price = price_cash_to_token target_inv y x in let dx_approx = 0n in // start at 0n to always be an underestimate if (x - dx_approx <= 0) then From 743d4c61f104f9f6d0c70da057d7239dab72c21a Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Thu, 26 Aug 2021 13:50:41 +0100 Subject: [PATCH 43/92] Correct default init values for target and drift --- test.mligo | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/test.mligo b/test.mligo index be27e993..e1ab4e17 100644 --- a/test.mligo +++ b/test.mligo @@ -43,8 +43,8 @@ let init_contracts (alice_bal : nat option) (bob_bal : nat option) (init_lqt : n let init_total_supply = match init_total_supply with | None -> 1_000_000n | Some s -> s in let init_token_pool = match init_token_pool with | None -> 10_000n | Some t -> t in let init_cash_pool = match init_cash_pool with | None -> 10_000n | Some c -> c in - let init_target = match init_target with | None -> 103n | Some t -> t in // TODO: this init value is off - let init_drift = match init_drift with | None -> 105 | Some d -> d in // TODO: this init value is off + let init_target = match init_target with | None -> (Bitwise.shift_left 2n 48n) | Some t -> t in // default target is 1 + let init_drift = match init_drift with | None -> 0 | Some d -> d in let last_drift_update = match last_drift_update with | None -> ("2021-01-01t10:10:10Z" : timestamp) | Some t -> t in let const_fee = match const_fee with | None -> (1n, 100n) | Some f -> f in let pending_pool_updates = match pending_pool_updates with | None -> 0n | Some p -> p in @@ -115,7 +115,8 @@ let init_contracts (alice_bal : nat option) (bob_bal : nat option) (init_lqt : n } in let update_ctez_addresses = Test.transfer_to_contract_exn ctez_entrypoint_set_addresses new_addresses 0tez in - (typed_addr_cfmm, typed_addr_ctez, typed_addr_fa12, typed_addr_lqt) + (typed_addr_cfmm, typed_addr_ctez, typed_addr_fa12, typed_addr_lqt, + addr_alice, addr_bob, addr_lqt, addr_dummy, addr_admin) (* ============================================================================= * Tests @@ -123,7 +124,8 @@ let init_contracts (alice_bal : nat option) (bob_bal : nat option) (init_lqt : n (* Run setup with default args; verify ctez storage is as expected *) let test_setup = - let (typed_addr_cfmm, typed_addr_ctez, typed_addr_fa12, typed_addr_lqt) = + let (typed_addr_cfmm, typed_addr_ctez, typed_addr_fa12, typed_addr_lqt, + addr_alice, addr_bob, addr_lqt, addr_dummy, addr_admin) = init_contracts (None : nat option) (* alice_bal *) (None : nat option) (* bob_bal *) @@ -159,7 +161,40 @@ let test_setup = (* Test that the difference equations in trades computed as expected *) -let test_diff_equations = () +let test_diff_equations = + let (typed_addr_cfmm, typed_addr_ctez, typed_addr_fa12, typed_addr_lqt, + addr_alice, addr_bob, addr_lqt, addr_dummy, addr_admin) = + init_contracts + (Some 500n : nat option) (* alice_bal *) + (Some 100n : nat option) (* bob_bal *) + (None : nat option) (* init_lqt *) + (None : nat option) (* init_total_supply *) + (None : nat option) (* init_token_pool *) + (None : nat option) (* init_cash_pool *) + (None : nat option) (* init_target *) + (None : int option) (* init_drift *) + (None : timestamp option) (* last_drift_update *) + (None : (nat * nat) option) (* const_fee *) + (None : nat option) (* pending_pool_updates *) + (None : (oven_handle, oven) big_map option) (* init_ovens *) + in + // get the TokenToCash entrypoint + let alice_source = Test.set_source addr_alice in + let trade_entrypoint : cash_to_token contract = + Test.to_entrypoint "cashToToken" typed_addr_cfmm in + let trade_data : cash_to_token = { + to_ = addr_alice; + minTokensBought = 1n; + deadline = ("3000-01-01t10:10:10Z" : timestamp); + rounds = 4; // default + } in + let alice_trade = + (Test.transfer_to_contract_exn trade_entrypoint trade_data 0tez) in () (* + // alice should trade 500ctez for tez + let ctez_fa12_storage = Test.get_storage typed_addr_fa12 in + let ctez_token_balances = ctez_fa12_storage.tokens in + Big_map.find_opt addr_alice ctez_token_balances *) + (* Tests compilation under different directives (may not be feasible in this framework) *) From 357836575443cc81e860f50fd00284ea4e2f63fb Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Thu, 26 Aug 2021 17:20:25 +0100 Subject: [PATCH 44/92] Keep everything positive in the newton calculation --- cfmm.mligo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index dc68cf09..db29105c 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -337,9 +337,9 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * let ax2 = a * a * x * x in let by2 = Bitwise.shift_left (y * y) 96n in let axp2 = a * a * xp * xp in let byp2 = Bitwise.shift_left (abs(yp * yp)) 96n in (* Newton descent formula *) - let num = x * y * (ax2 + by2) - xp * yp * (axp2 + byp2) in + let num = abs(xp * yp * (axp2 + byp2) - x * y * (ax2 + by2)) in // num is always positive, even without abs let denom = xp * (axp2 + 3 * byp2) in - let adjust = -((-num) / denom) in // double negative so the division rounds down, instead of up + let adjust = (num) / denom in let new_dy_approx = abs (dy_approx + adjust) in let next_round = (rounds - 1) in newton_dx_to_dy (x,y,dx,new_dy_approx,target,next_round) From 67452afcf3c542dd0d5d07dcb54783cc01ab377f Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Fri, 27 Aug 2021 10:54:16 +0100 Subject: [PATCH 45/92] Resolve some TODOs in the code --- ctez.mligo | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/ctez.mligo b/ctez.mligo index 4f68225a..ce872146 100644 --- a/ctez.mligo +++ b/ctez.mligo @@ -171,12 +171,9 @@ let mint_or_burn (s : storage) (p : mint_or_burn) : result = ([Tezos.transaction (p.quantity, Tezos.sender) 0mutez ctez_mint_or_burn], s) let get_target (storage : storage) (callback : nat contract) : result = - // todo: any security on the contract itself? ([Tezos.transaction storage.target 0mutez callback], storage) -(* todo: restore when ligo interpret is fixed - let cfmm_price (storage : storage) (tez : tez) (token : nat) : result = *) -let cfmm_price (storage, price : storage * nat) : result = +let cfmm_price (storage : storage) (price : nat) : result = if Tezos.sender <> storage.cfmm_address then (failwith error_CALLER_MUST_BE_CFMM : result) else @@ -215,7 +212,7 @@ let main (p, s : parameter * storage) : result = | Create d -> (create s d : result) | Liquidate l -> (liquidate s l : result) | Mint_or_burn xs -> (mint_or_burn s xs : result) - | Cfmm_price x -> (cfmm_price (s, x) : result) + | Cfmm_price x -> (cfmm_price s x : result) | Set_addresses xs -> (set_addresses s xs : result) | Get_target t -> (get_target s t : result) From cdc3970607f9f451570aec78ad43c033d0c34fe1 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Fri, 27 Aug 2021 13:39:02 +0100 Subject: [PATCH 46/92] Fix txn test --- test.mligo | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/test.mligo b/test.mligo index e1ab4e17..6d716662 100644 --- a/test.mligo +++ b/test.mligo @@ -43,10 +43,10 @@ let init_contracts (alice_bal : nat option) (bob_bal : nat option) (init_lqt : n let init_total_supply = match init_total_supply with | None -> 1_000_000n | Some s -> s in let init_token_pool = match init_token_pool with | None -> 10_000n | Some t -> t in let init_cash_pool = match init_cash_pool with | None -> 10_000n | Some c -> c in - let init_target = match init_target with | None -> (Bitwise.shift_left 2n 48n) | Some t -> t in // default target is 1 + let init_target = match init_target with | None -> 131072n | Some t -> t in // default target is 1 (Bitwise.shift_left 2n 48n) let init_drift = match init_drift with | None -> 0 | Some d -> d in let last_drift_update = match last_drift_update with | None -> ("2021-01-01t10:10:10Z" : timestamp) | Some t -> t in - let const_fee = match const_fee with | None -> (1n, 100n) | Some f -> f in + let const_fee = match const_fee with | None -> (1n, 1n) | Some f -> f in // no default fee let pending_pool_updates = match pending_pool_updates with | None -> 0n | Some p -> p in let init_ovens = match init_ovens with | None -> (Big_map.empty : (oven_handle, oven) big_map) | Some o -> o in @@ -102,19 +102,26 @@ let init_contracts (alice_bal : nat option) (bob_bal : nat option) (init_lqt : n tokenAddress = Tezos.address (Test.to_contract typed_addr_fa12) ; lqtAddress = Tezos.address (Test.to_contract typed_addr_lqt) ; } in - let (typed_addr_cfmm, program_cfmm, size_cfmm) = Test.originate main_cfmm cfmm_init_storage 0tez in + let (typed_addr_cfmm, program_cfmm, size_cfmm) = Test.originate main_cfmm cfmm_init_storage (1tez * init_cash_pool) in // update ctez's storage using its set_addresses entrypoints - let ctez_entrypoint_set_addresses = + let entrypoint_set_addresses = ((Test.to_entrypoint "set_addresses" typed_addr_ctez) : set_addresses contract) in let untyped_addr_cfmm = Tezos.address (Test.to_contract typed_addr_cfmm) in let untyped_addr_fa12 = Tezos.address (Test.to_contract typed_addr_fa12) in - let new_addresses : set_addresses = { + let txndata_set_addresses : set_addresses = { cfmm_address=untyped_addr_cfmm; ctez_fa12_address=untyped_addr_fa12; } in - let update_ctez_addresses = Test.transfer_to_contract_exn ctez_entrypoint_set_addresses new_addresses 0tez in - + let update_ctez_addresses = Test.transfer_to_contract_exn entrypoint_set_addresses txndata_set_addresses 0tez in + + // mint tokens in the amount of init_token_pool for the cfmm contract + let admin_source = Test.set_source addr_admin in + let addr_cfmm = Tezos.address (Test.to_contract typed_addr_cfmm) in + let entrypoint_mint : mintOrBurn contract = Test.to_entrypoint "mintOrBurn" typed_addr_fa12 in + let txndata_mint : mintOrBurn = { quantity=int(init_token_pool) ; target=addr_cfmm } in + let txn_mint = Test.transfer_to_contract_exn entrypoint_mint txndata_mint 0tez in + (typed_addr_cfmm, typed_addr_ctez, typed_addr_fa12, typed_addr_lqt, addr_alice, addr_bob, addr_lqt, addr_dummy, addr_admin) @@ -165,8 +172,8 @@ let test_diff_equations = let (typed_addr_cfmm, typed_addr_ctez, typed_addr_fa12, typed_addr_lqt, addr_alice, addr_bob, addr_lqt, addr_dummy, addr_admin) = init_contracts - (Some 500n : nat option) (* alice_bal *) - (Some 100n : nat option) (* bob_bal *) + (Some 0n : nat option) (* alice_bal *) + (Some 0n : nat option) (* bob_bal *) (None : nat option) (* init_lqt *) (None : nat option) (* init_total_supply *) (None : nat option) (* init_token_pool *) @@ -188,12 +195,13 @@ let test_diff_equations = deadline = ("3000-01-01t10:10:10Z" : timestamp); rounds = 4; // default } in + let trade_amt = 10tez in let alice_trade = - (Test.transfer_to_contract_exn trade_entrypoint trade_data 0tez) in () (* + (Test.transfer_to_contract_exn trade_entrypoint trade_data trade_amt) in // alice should trade 500ctez for tez - let ctez_fa12_storage = Test.get_storage typed_addr_fa12 in - let ctez_token_balances = ctez_fa12_storage.tokens in - Big_map.find_opt addr_alice ctez_token_balances *) + let ctez_fa12_storage = Test.get_storage typed_addr_fa12 in + let ctez_token_balances = ctez_fa12_storage.tokens in + Big_map.find_opt addr_alice ctez_token_balances From 22bbb67547afaa16c756845473d61aa5102b542f Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Wed, 1 Sep 2021 11:01:08 +0100 Subject: [PATCH 47/92] Updating tests. --- test.mligo | 109 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 88 insertions(+), 21 deletions(-) diff --git a/test.mligo b/test.mligo index 6d716662..9bad3eb9 100644 --- a/test.mligo +++ b/test.mligo @@ -28,6 +28,15 @@ type cfmm_parameter = parameter type cfmm_result = result +(* ============================================================================= + * Some Aux Functions + * ============================================================================= *) + +let mutez_to_tez (x : tez) = 1mutez * (x / 1_000_000mutez) + + +let test_sanity = mutez_to_tez 1_000_000mutez + (* ============================================================================= * Generic Setup that Initiates All Contracts * ============================================================================= *) @@ -37,24 +46,24 @@ let init_contracts (alice_bal : nat option) (bob_bal : nat option) (init_lqt : n (init_drift : int option) (last_drift_update : timestamp option) (const_fee : (nat * nat) option) (pending_pool_updates : nat option) (init_ovens : (oven_handle, oven) big_map option) = // set defaults for optional args - let alice_bal = match alice_bal with | None -> 100n | Some b -> b in - let bob_bal = match bob_bal with | None -> 100n | Some b -> b in - let init_lqt = match init_lqt with | None -> 10n | Some l -> l in - let init_total_supply = match init_total_supply with | None -> 1_000_000n | Some s -> s in - let init_token_pool = match init_token_pool with | None -> 10_000n | Some t -> t in - let init_cash_pool = match init_cash_pool with | None -> 10_000n | Some c -> c in - let init_target = match init_target with | None -> 131072n | Some t -> t in // default target is 1 (Bitwise.shift_left 2n 48n) - let init_drift = match init_drift with | None -> 0 | Some d -> d in - let last_drift_update = match last_drift_update with | None -> ("2021-01-01t10:10:10Z" : timestamp) | Some t -> t in - let const_fee = match const_fee with | None -> (1n, 1n) | Some f -> f in // no default fee - let pending_pool_updates = match pending_pool_updates with | None -> 0n | Some p -> p in + let alice_bal = match alice_bal with | None -> 100n | Some b -> b in + let bob_bal = match bob_bal with | None -> 100n | Some b -> b in + let init_lqt = match init_lqt with | None -> 10n | Some l -> l in + let init_total_supply = match init_total_supply with | None -> 1_000_000_000_000_000n | Some s -> s in + let init_token_pool = match init_token_pool with | None -> 1_000_000_000_000n | Some t -> t in // muctez, e.g. 1_000_000 ctez + let init_cash_pool = match init_cash_pool with | None -> 1_000_000_000_000n | Some c -> c in // mutez, e.g. 1_000_000 tez + let init_target = match init_target with | None -> 1_000n | Some t -> t in // default target is 1 (Bitwise.shift_left 1n 48n) + let init_drift = match init_drift with | None -> 0 | Some d -> d in + let last_drift_update = match last_drift_update with | None -> ("2000-01-01t10:10:10Z" : timestamp) | Some t -> t in + let const_fee = match const_fee with | None -> (1000n, 1000n)| Some f -> f in // no default fee + let pending_pool_updates = match pending_pool_updates with | None -> 0n | Some p -> p in let init_ovens = match init_ovens with | None -> (Big_map.empty : (oven_handle, oven) big_map) | Some o -> o in // generate some implicit addresses let reset_state_unit = Test.reset_state 5n ([] : nat list) in let (addr_alice, addr_bob, addr_lqt, addr_dummy, addr_admin) = (Test.nth_bootstrap_account 0, Test.nth_bootstrap_account 1, Test.nth_bootstrap_account 2, - Test.nth_bootstrap_account 3, Test.nth_bootstrap_account 4) + Test.nth_bootstrap_account 3, Test.nth_bootstrap_account 4) in let null_address = ("tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" : address) in @@ -80,7 +89,7 @@ let init_contracts (alice_bal : nat option) (bob_bal : nat option) (init_lqt : n // ctez contract let ctez_init_storage : ctez_storage = { ovens = init_ovens; - target = 103n;//(103n,100n); + target = init_target; drift = init_drift; last_drift_update = last_drift_update; ctez_fa12_address = null_address; @@ -102,7 +111,7 @@ let init_contracts (alice_bal : nat option) (bob_bal : nat option) (init_lqt : n tokenAddress = Tezos.address (Test.to_contract typed_addr_fa12) ; lqtAddress = Tezos.address (Test.to_contract typed_addr_lqt) ; } in - let (typed_addr_cfmm, program_cfmm, size_cfmm) = Test.originate main_cfmm cfmm_init_storage (1tez * init_cash_pool) in + let (typed_addr_cfmm, program_cfmm, size_cfmm) = Test.originate main_cfmm cfmm_init_storage (1mutez * init_cash_pool) in // update ctez's storage using its set_addresses entrypoints let entrypoint_set_addresses = @@ -152,8 +161,8 @@ let test_setup = let untyped_addr_cfmm = Tezos.address (Test.to_contract typed_addr_cfmm) in let expected_ctez_storage : ctez_storage = { ovens = (Big_map.empty : (oven_handle, oven) big_map); - target = 103n;//(103n,100n); // TODO go up and down 5% in 0.1% increments - drift = 105;//(105,100); // TODO should this actually be a pair? + target = 1n; // TODO go up and down 5% in 0.1% increments + drift = 0; // TODO should this actually be a pair? last_drift_update = ("2000-01-01t10:10:10Z" : timestamp); // 5 mins (300 sec), this should vary I think ctez_fa12_address = untyped_addr_fa12; cfmm_address = untyped_addr_cfmm; @@ -168,7 +177,7 @@ let test_setup = (* Test that the difference equations in trades computed as expected *) -let test_diff_equations = +let test_cash_to_token = let (typed_addr_cfmm, typed_addr_ctez, typed_addr_fa12, typed_addr_lqt, addr_alice, addr_bob, addr_lqt, addr_dummy, addr_admin) = init_contracts @@ -181,7 +190,7 @@ let test_diff_equations = (None : nat option) (* init_target *) (None : int option) (* init_drift *) (None : timestamp option) (* last_drift_update *) - (None : (nat * nat) option) (* const_fee *) + (Some (1000n,1000n) : (nat * nat) option) (* const_fee *) (None : nat option) (* pending_pool_updates *) (None : (oven_handle, oven) big_map option) (* init_ovens *) in @@ -189,19 +198,77 @@ let test_diff_equations = let alice_source = Test.set_source addr_alice in let trade_entrypoint : cash_to_token contract = Test.to_entrypoint "cashToToken" typed_addr_cfmm in + let trade_amt = 100_000_000mutez in let trade_data : cash_to_token = { to_ = addr_alice; - minTokensBought = 1n; + minTokensBought = abs (100_000_000n - 1_000n); // within 0.001ctez of 1:1 trade deadline = ("3000-01-01t10:10:10Z" : timestamp); rounds = 4; // default } in - let trade_amt = 10tez in + let alice_balance_old = Test.get_balance addr_alice in let alice_trade = (Test.transfer_to_contract_exn trade_entrypoint trade_data trade_amt) in // alice should trade 500ctez for tez let ctez_fa12_storage = Test.get_storage typed_addr_fa12 in let ctez_token_balances = ctez_fa12_storage.tokens in - Big_map.find_opt addr_alice ctez_token_balances + let alice_balance = Test.get_balance addr_alice in + ((Big_map.find_opt addr_alice ctez_token_balances), (alice_balance / 1mutez - alice_balance_old / 1mutez)) + + + +let test_token_to_cash = + let alice_initial_bal = 1_000_000_000n in // in muctez; == 1_000 ctez + let (typed_addr_cfmm, typed_addr_ctez, typed_addr_fa12, typed_addr_lqt, + addr_alice, addr_bob, addr_lqt, addr_dummy, addr_admin) = + init_contracts + (Some alice_initial_bal : nat option) (* alice_bal *) + (Some 0n : nat option) (* bob_bal *) + (None : nat option) (* init_lqt *) + (None : nat option) (* init_total_supply *) + (None : nat option) (* init_token_pool *) + (None : nat option) (* init_cash_pool *) + (None : nat option) (* init_target *) + (None : int option) (* init_drift *) + (None : timestamp option) (* last_drift_update *) + (Some (1000n,1000n) : (nat * nat) option) (* const_fee *) + (None : nat option) (* pending_pool_updates *) + (None : (oven_handle, oven) big_map option) (* init_ovens *) + in + // get the TokenToCash and Approve entrypoints + let alice_source = Test.set_source addr_alice in + let alice_txn_amt = alice_initial_bal in + let addr_cfmm = Tezos.address (Test.to_contract typed_addr_cfmm) in + let alice_balance_old = Test.get_balance addr_alice in + let addr_cfmm_balance_old = Test.get_balance addr_cfmm in + let bob_balance_old = Test.get_balance addr_bob in + + let trade_entrypoint : token_to_cash contract = + Test.to_entrypoint "tokenToCash" typed_addr_cfmm in + let trade_data : token_to_cash = { + to_ = addr_bob; + tokensSold = alice_txn_amt; // in muctez + minCashBought = abs (alice_txn_amt - 1_000n); // (plus or) minus .1 tez of 1:1 trade + deadline = ("3000-01-01t10:10:10Z" : timestamp); + rounds = 4; // default + } in + + let approve_entrypoint : approve contract = + Test.to_entrypoint "approve" typed_addr_fa12 in + let approve_data : approve = { + spender = addr_cfmm ; + value = alice_txn_amt ; + } in + + let alice_approve = + (Test.transfer_to_contract_exn approve_entrypoint approve_data 0tez) in + let alice_trade = + (Test.transfer_to_contract_exn trade_entrypoint trade_data 0tez) in + + let alice_balance = Test.get_balance addr_alice in + let addr_cfmm_balance = Test.get_balance addr_cfmm in + let bob_balance = Test.get_balance addr_bob in + ((bob_balance / 1mutez) - (bob_balance_old / 1mutez) , (alice_balance / 1mutez) - (alice_balance_old / 1mutez), (addr_cfmm_balance / 1mutez) - (addr_cfmm_balance_old / 1mutez)) + From 88b1466bb0fe4dbe3df39b91c663b7550f61cc65 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Wed, 1 Sep 2021 16:51:38 +0100 Subject: [PATCH 48/92] Adding parameterized trades --- test.mligo | 70 ++++++++++++++++++++++++++++------------------- test_params.mligo | 21 ++++++++++++++ unit_test.mligo | 50 +++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 28 deletions(-) create mode 100644 test_params.mligo create mode 100644 unit_test.mligo diff --git a/test.mligo b/test.mligo index 9bad3eb9..580f049d 100644 --- a/test.mligo +++ b/test.mligo @@ -27,16 +27,14 @@ type cfmm_storage = storage type cfmm_parameter = parameter type cfmm_result = result +#include "test_params.mligo" (* ============================================================================= * Some Aux Functions * ============================================================================= *) -let mutez_to_tez (x : tez) = 1mutez * (x / 1_000_000mutez) -let test_sanity = mutez_to_tez 1_000_000mutez - (* ============================================================================= * Generic Setup that Initiates All Contracts * ============================================================================= *) @@ -138,7 +136,7 @@ let init_contracts (alice_bal : nat option) (bob_bal : nat option) (init_lqt : n * Tests * ============================================================================= *) -(* Run setup with default args; verify ctez storage is as expected *) +(******** STANDARD SETUP ********) let test_setup = let (typed_addr_cfmm, typed_addr_ctez, typed_addr_fa12, typed_addr_lqt, addr_alice, addr_bob, addr_lqt, addr_dummy, addr_admin) = @@ -169,15 +167,16 @@ let test_setup = } in let actual_ctez_storage = Test.get_storage typed_addr_ctez in - // assertions to verify storage is as expected + // assertions to verify storage is as expected // TODO : when Bitwise works, send storages to bytes and compare ( assert (expected_ctez_storage.cfmm_address = actual_ctez_storage.cfmm_address), assert (expected_ctez_storage.ctez_fa12_address = actual_ctez_storage.ctez_fa12_address) ) -(* Test that the difference equations in trades computed as expected *) -let test_cash_to_token = +(******** DIFFERENCE EQUATIONS ********) +(** cash to token **) +let trade_cash_to_token_test (x, y, dx, target, rounds, const_fee : nat * nat * nat * nat * int * (nat * nat)) = let (typed_addr_cfmm, typed_addr_ctez, typed_addr_fa12, typed_addr_lqt, addr_alice, addr_bob, addr_lqt, addr_dummy, addr_admin) = init_contracts @@ -185,12 +184,12 @@ let test_cash_to_token = (Some 0n : nat option) (* bob_bal *) (None : nat option) (* init_lqt *) (None : nat option) (* init_total_supply *) - (None : nat option) (* init_token_pool *) - (None : nat option) (* init_cash_pool *) - (None : nat option) (* init_target *) + (Some y : nat option) (* init_token_pool *) + (Some x : nat option) (* init_cash_pool *) + (Some target : nat option) (* init_target *) (None : int option) (* init_drift *) (None : timestamp option) (* last_drift_update *) - (Some (1000n,1000n) : (nat * nat) option) (* const_fee *) + (Some const_fee : (nat * nat) option) (* const_fee *) (None : nat option) (* pending_pool_updates *) (None : (oven_handle, oven) big_map option) (* init_ovens *) in @@ -198,12 +197,12 @@ let test_cash_to_token = let alice_source = Test.set_source addr_alice in let trade_entrypoint : cash_to_token contract = Test.to_entrypoint "cashToToken" typed_addr_cfmm in - let trade_amt = 100_000_000mutez in + let trade_amt = (dx * 1mutez) in let trade_data : cash_to_token = { to_ = addr_alice; - minTokensBought = abs (100_000_000n - 1_000n); // within 0.001ctez of 1:1 trade + minTokensBought = 0n; deadline = ("3000-01-01t10:10:10Z" : timestamp); - rounds = 4; // default + rounds = rounds; } in let alice_balance_old = Test.get_balance addr_alice in let alice_trade = @@ -211,13 +210,19 @@ let test_cash_to_token = // alice should trade 500ctez for tez let ctez_fa12_storage = Test.get_storage typed_addr_fa12 in let ctez_token_balances = ctez_fa12_storage.tokens in - let alice_balance = Test.get_balance addr_alice in - ((Big_map.find_opt addr_alice ctez_token_balances), (alice_balance / 1mutez - alice_balance_old / 1mutez)) + let (fee_num, fee_denom) = const_fee in + match (Big_map.find_opt addr_alice ctez_token_balances) with + | None -> (failwith "Incomplete Cash to Token Transfer" : unit) + | Some bal -> assert (bal = fee_num * (trade_dcash_for_dtoken x y dx target rounds) / fee_denom) +let test_cash_to_token = + let test_result = List.map trade_cash_to_token_test trade_params in + () // if it reaches this, everything passed -let test_token_to_cash = - let alice_initial_bal = 1_000_000_000n in // in muctez; == 1_000 ctez +(** token to cash **) +let trade_token_to_cash_test (x, y, dy, target, rounds, const_fee : nat * nat * nat * nat * int * (nat * nat)) = + let alice_initial_bal = dy in // in muctez; == 1_000 ctez let (typed_addr_cfmm, typed_addr_ctez, typed_addr_fa12, typed_addr_lqt, addr_alice, addr_bob, addr_lqt, addr_dummy, addr_admin) = init_contracts @@ -225,12 +230,12 @@ let test_token_to_cash = (Some 0n : nat option) (* bob_bal *) (None : nat option) (* init_lqt *) (None : nat option) (* init_total_supply *) - (None : nat option) (* init_token_pool *) - (None : nat option) (* init_cash_pool *) + (Some y : nat option) (* init_token_pool *) + (Some x : nat option) (* init_cash_pool *) (None : nat option) (* init_target *) (None : int option) (* init_drift *) (None : timestamp option) (* last_drift_update *) - (Some (1000n,1000n) : (nat * nat) option) (* const_fee *) + (Some const_fee : (nat * nat) option) (* const_fee *) (None : nat option) (* pending_pool_updates *) (None : (oven_handle, oven) big_map option) (* init_ovens *) in @@ -242,16 +247,18 @@ let test_token_to_cash = let addr_cfmm_balance_old = Test.get_balance addr_cfmm in let bob_balance_old = Test.get_balance addr_bob in + // trade entrypoint let trade_entrypoint : token_to_cash contract = Test.to_entrypoint "tokenToCash" typed_addr_cfmm in let trade_data : token_to_cash = { - to_ = addr_bob; + to_ = addr_bob; // send to bob so that gas costs don't factor into the change in balance tokensSold = alice_txn_amt; // in muctez - minCashBought = abs (alice_txn_amt - 1_000n); // (plus or) minus .1 tez of 1:1 trade + minCashBought = 0n; deadline = ("3000-01-01t10:10:10Z" : timestamp); - rounds = 4; // default + rounds = rounds; } in + // approve entrypoint let approve_entrypoint : approve contract = Test.to_entrypoint "approve" typed_addr_fa12 in let approve_data : approve = { @@ -259,24 +266,31 @@ let test_token_to_cash = value = alice_txn_amt ; } in + // execute and test let alice_approve = (Test.transfer_to_contract_exn approve_entrypoint approve_data 0tez) in let alice_trade = (Test.transfer_to_contract_exn trade_entrypoint trade_data 0tez) in - let alice_balance = Test.get_balance addr_alice in let addr_cfmm_balance = Test.get_balance addr_cfmm in let bob_balance = Test.get_balance addr_bob in - ((bob_balance / 1mutez) - (bob_balance_old / 1mutez) , (alice_balance / 1mutez) - (alice_balance_old / 1mutez), (addr_cfmm_balance / 1mutez) - (addr_cfmm_balance_old / 1mutez)) + let bob_balance_delta = bob_balance / 1mutez - bob_balance_old / 1mutez in + let (fee_num, fee_denom) = const_fee in + assert (abs bob_balance_delta = fee_num * (trade_dtoken_for_dcash x y dy target rounds) / fee_denom) +let test_token_to_cash = + let test_result = List.map trade_token_to_cash_test trade_params in + () // if it reaches this, everything passed -(* Tests compilation under different directives (may not be feasible in this framework) *) +(******** DIRECTIVES ********) let test_directives = () +// Tests compilation under different directives (may not be feasible in this framework) -(* Checks that drift and target grew at expected rate after x mins *) +(******** DRIFT ********) let test_price = () + // Checks that drift and target grew at expected rate after x mins // 5 mins, or 300 secs will be default // target should go up and down 5% in 0.1% increments \ No newline at end of file diff --git a/test_params.mligo b/test_params.mligo new file mode 100644 index 00000000..0d49ed84 --- /dev/null +++ b/test_params.mligo @@ -0,0 +1,21 @@ +// params: x y dx target rounds OR y x dy target rounds +let trade_params : (nat * nat * nat * nat * int * (nat * nat)) list = [ + (1_000_000_000_000n, 1_000_000_000_000n, 100_000_000n, 1_000n, 4, (1_000n, 1_300n)) ; // defaults + // TODO : populate +] + +// output: tokens +let expected_cash_to_token : nat list = [ + 99_999_999n ; +] + +// output: cash +let expected_token_to_cash : nat list = [ + 99_999_999n ; +] + + +// fee variation +let fees : (nat * nat) list = [ + (1_000n,1_000n) ; +] \ No newline at end of file diff --git a/unit_test.mligo b/unit_test.mligo new file mode 100644 index 00000000..d27df237 --- /dev/null +++ b/unit_test.mligo @@ -0,0 +1,50 @@ +(* This is a unit testing framework for ctez *) + +(* ============================================================================= + * Contract Templates + * ============================================================================= *) + +#include "fa12.mligo" +let main_fa12 = main +type fa12_storage = storage +type fa12_parameter = parameter +type fa12_result = result + +let main_lqt = main +type lqt_storage = storage +type lqt_parameter = parameter +type lqt_result = result + +#include "ctez.mligo" +let main_ctez = main +type ctez_storage = storage +type ctez_parameter = parameter +type ctez_result = result + +#include "cfmm.mligo" +let main_cfmm = main +type cfmm_storage = storage +type cfmm_parameter = parameter +type cfmm_result = result + +#include "test_params.mligo" + +(* ============================================================================= + * Contract Templates + * ============================================================================= *) + +(* Newton tests *) +let test_newton = + newton_dx_to_dy (1_000_000n, 1_000_000n, 100n, 0n, (1000n, 1000n), 4) + + +(* TODO : test these against the python code *) +let test_dtoken_to_dcash = + let trade = fun (x, y, dx, target, rounds, (fee_num, fee_denom) : nat * nat * nat * nat * int * (nat * nat)) -> fee_num * (trade_dtoken_for_dcash x y dx target rounds) / fee_denom in + let traded = List.map trade trade_params in + traded //assert (traded = expected_token_to_cash) + +let test_dcash_to_dtoken = + let trade = fun (x, y, dy, target, rounds, (fee_num, fee_denom) : nat * nat * nat * nat * int * (nat * nat)) -> fee_num * trade_dcash_for_dtoken x y dy target rounds / fee_denom in + let traded = List.map trade trade_params in + traded //assert (traded = expected_token_to_cash) \ No newline at end of file From 2de0183c18675fcb2bc24dc280de486cea81bafe Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Fri, 3 Sep 2021 12:49:17 +0100 Subject: [PATCH 49/92] Compilation with #if ORACLE directive --- cfmm.mligo | 33 +++++++++--------- ctez.mligo | 16 +++++++-- test.mligo | 99 ++++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 119 insertions(+), 29 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index db29105c..2ad9725b 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -1,5 +1,5 @@ (* Pick one of CASH_IS_TEZ, CASH_IS_FA2, CASH_IS_FA12. tokenToToken isn't supported for CASH_IS_FA12 *) -//#define CASH_IS_TEZ +#define CASH_IS_TEZ //#define CASH_IS_FA2 //#define CASH_IS_FA12 @@ -8,7 +8,7 @@ (* To support baking *) //#define HAS_BAKER (* To push prices to some consumer contract once per block *) -//#define ORACLE +#define ORACLE (* ============================================================================ @@ -74,7 +74,7 @@ type set_baker = } #endif -(* (a,b) such that a/b is the target price from the ctez contract *) +(* a such that (Bitwise.shift_right a 48n) is the target price from the ctez contract *) type ctez_target = nat (* getbalance update types for fa12 and fa2 *) @@ -303,8 +303,9 @@ let cash_transfer (storage : storage) (from : address) (to_ : address) (cash_amo #endif #endif -(* Isoutility and Difference Equations *) +(* Difference Equations *) // The Isoutility Function in https://hackmd.io/MkPSYXDsTf-giBprcDrc3w +(* // b is 2^48 below, implemented as bitwise shifts let isoutility (target, cash, token : nat * nat * nat) : nat = let x = cash in @@ -324,7 +325,7 @@ let price_cash_to_token (target : nat) (cash : nat) (token : nat) : nat = let num = y * (3n * ax2 + by2) in let denom = x * (ax2 + 3n * by2) in num/denom - +*) // A function to transfer assets while maintaining a constant isoutility let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * nat * nat * int) : nat = if (rounds <= 0) (* Newton generally converges in 4 rounds, so we bound computation there *) @@ -334,12 +335,12 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * let a = target in let xp = x + dx in let yp = y - dy_approx in - let ax2 = a * a * x * x in let by2 = Bitwise.shift_left (y * y) 96n in - let axp2 = a * a * xp * xp in let byp2 = Bitwise.shift_left (abs(yp * yp)) 96n in + let ax2 = a * a * x * x in let by2 = Bitwise.shift_left (y * y) 96n in // (y * y) * b * b + let axp2 = a * a * xp * xp in let byp2 = Bitwise.shift_left (abs(yp * yp)) 96n in // (abs(yp * yp)) * b * b (* Newton descent formula *) - let num = abs(xp * yp * (axp2 + byp2) - x * y * (ax2 + by2)) in // num is always positive, even without abs + let num = abs (xp * yp * (axp2 + byp2) - x * y * (ax2 + by2)) in // num is always positive, even without abs let denom = xp * (axp2 + 3 * byp2) in - let adjust = (num) / denom in + let adjust = num / denom in let new_dy_approx = abs (dy_approx + adjust) in let next_round = (rounds - 1) in newton_dx_to_dy (x,y,dx,new_dy_approx,target,next_round) @@ -353,6 +354,7 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * // A function that outputs dy (diff_token) given x, y, and dx let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat) (rounds : int) : nat = let dy_approx = 0n in // start at 0n to always be an underestimate + if (y - dy_approx <= 0) then (failwith error_TOKEN_POOL_MINUS_TOKENS_WITHDRAWN_IS_NEGATIVE : nat) @@ -361,11 +363,10 @@ let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat) (rounds // A function that outputs dx (diff_cash) given target, x, y, and dy let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat) (rounds : int) : nat = - let a = target in - let b2 = (Bitwise.shift_left 2n 96n) in - (* Will get error if a = 0 to begin with, but if that's the case we have bigger fish to fry *) - let target_inv = b2 / a in // when later divided by b, target_inv will be b / a let dx_approx = 0n in // start at 0n to always be an underestimate + let target_inv = (Bitwise.shift_left 1n 96n) / target in // b^2 / a + (* Will get error if a = 0 to begin with, but if that's the case we have bigger fish to fry *) + if (x - dx_approx <= 0) then (failwith error_CASH_POOL_MINUS_CASH_WITHDRAWN_IS_NEGATIVE : nat) @@ -497,7 +498,7 @@ let cash_to_token (param : cash_to_token) (storage : storage) = cashSold = cashSold ; #endif deadline = deadline ; - rounds = rounds } = param in + rounds = rounds } = param in #if CASH_IS_TEZ let cashSold = mutez_to_natural Tezos.amount in @@ -570,9 +571,9 @@ let token_to_cash (param : token_to_cash) (storage : storage) = let op_token = token_transfer storage Tezos.sender Tezos.self_address tokensSold in #if CASH_IS_TEZ - let op_cash = cash_transfer to_ cash_bought in + let op_cash = cash_transfer to_ cash_bought in #else - let op_cash = cash_transfer storage Tezos.self_address to_ cash_bought in + let op_cash = cash_transfer storage Tezos.self_address to_ cash_bought in #endif let new_cashPool = match is_nat (storage.cashPool - cash_bought) with | None -> (failwith error_ASSERTION_VIOLATED_CASH_BOUGHT_SHOULD_BE_LESS_THAN_CASHPOOL : nat) diff --git a/ctez.mligo b/ctez.mligo index ce872146..bb81ac12 100644 --- a/ctez.mligo +++ b/ctez.mligo @@ -25,7 +25,7 @@ type parameter = | Liquidate of liquidate | Register_deposit of register_deposit | Mint_or_burn of mint_or_burn - | Cfmm_price of nat + | Cfmm_price of nat * nat | Set_addresses of set_addresses | Get_target of nat contract @@ -86,6 +86,15 @@ let get_ctez_mint_or_burn (fa12_address : address) : (int * address) contract = | None -> (failwith error_CTEZ_FA12_CONTRACT_MISSING_MINT_OR_BURN_ENTRYPOINT : (int * address) contract) | Some c -> c +let get_price (target : nat) (cash : nat) (token : nat) : nat = + let (x,y) = (cash, token) in + let a = target in + let ax2 = x * x * a * a in + let by2 = Bitwise.shift_left (y * y) 96n in + let num = y * (3n * ax2 + by2) in + let denom = x * (ax2 + 3n * by2) in + num/denom + (* Entrypoint Functions *) let create (s : storage) (create : create) : result = @@ -173,7 +182,7 @@ let mint_or_burn (s : storage) (p : mint_or_burn) : result = let get_target (storage : storage) (callback : nat contract) : result = ([Tezos.transaction storage.target 0mutez callback], storage) -let cfmm_price (storage : storage) (price : nat) : result = +let cfmm_price (storage : storage) (tez : nat) (token : nat) : result = if Tezos.sender <> storage.cfmm_address then (failwith error_CALLER_MUST_BE_CFMM : result) else @@ -191,6 +200,7 @@ let cfmm_price (storage : storage) (price : nat) : result = for each day over or under the target by more than 1/64th. *) + let price : nat = get_price target tez token in let target_less_price : int = target - price in let d_drift = let x = Bitwise.shift_left (abs (target_less_price * target_less_price)) 10n in @@ -212,7 +222,7 @@ let main (p, s : parameter * storage) : result = | Create d -> (create s d : result) | Liquidate l -> (liquidate s l : result) | Mint_or_burn xs -> (mint_or_burn s xs : result) - | Cfmm_price x -> (cfmm_price s x : result) + | Cfmm_price (x,y) -> (cfmm_price s x y : result) | Set_addresses xs -> (set_addresses s xs : result) | Get_target t -> (get_target s t : result) diff --git a/test.mligo b/test.mligo index 580f049d..2dea2859 100644 --- a/test.mligo +++ b/test.mligo @@ -33,6 +33,25 @@ type cfmm_result = result * Some Aux Functions * ============================================================================= *) +// Returns the price dy/dx of the isoutility function, i.e. a map by multiplication ∆x => ∆y, at a given point (x,y) +let price_cash_to_token (target : nat) (cash : nat) (token : nat) : nat = + let (x,y) = (cash, token) in + let a = target in + let ax2 = x * x * a * a in + let by2 = Bitwise.shift_left (y * y) 96n in + let num = y * (3n * ax2 + by2) in + let denom = x * (ax2 + 3n * by2) in + num/denom + +// The isoutility function used +let isoutility (target, cash, token : nat * nat * nat) : nat = + let x = cash in + let y = token in + let a = target in + let a2 = a * a in + let ax2 = a2 * x * x in + let by2 = Bitwise.shift_left (y * y) 96n in + (Bitwise.shift_right (abs((a * x * y) * (ax2 + by2) / (2 * a2))) 48n) (* ============================================================================= @@ -43,6 +62,9 @@ let init_contracts (alice_bal : nat option) (bob_bal : nat option) (init_lqt : n (init_token_pool : nat option) (init_cash_pool : nat option) (init_target : nat option) (init_drift : int option) (last_drift_update : timestamp option) (const_fee : (nat * nat) option) (pending_pool_updates : nat option) (init_ovens : (oven_handle, oven) big_map option) = + // set time + let now = ("2000-01-01t10:10:10Z" : timestamp) in + // set defaults for optional args let alice_bal = match alice_bal with | None -> 100n | Some b -> b in let bob_bal = match bob_bal with | None -> 100n | Some b -> b in @@ -50,9 +72,9 @@ let init_contracts (alice_bal : nat option) (bob_bal : nat option) (init_lqt : n let init_total_supply = match init_total_supply with | None -> 1_000_000_000_000_000n | Some s -> s in let init_token_pool = match init_token_pool with | None -> 1_000_000_000_000n | Some t -> t in // muctez, e.g. 1_000_000 ctez let init_cash_pool = match init_cash_pool with | None -> 1_000_000_000_000n | Some c -> c in // mutez, e.g. 1_000_000 tez - let init_target = match init_target with | None -> 1_000n | Some t -> t in // default target is 1 (Bitwise.shift_left 1n 48n) + let init_target = match init_target with | None -> (Bitwise.shift_left 1n 48n) | Some t -> t in // default target is 1 (Bitwise.shift_left 1n 48n) let init_drift = match init_drift with | None -> 0 | Some d -> d in - let last_drift_update = match last_drift_update with | None -> ("2000-01-01t10:10:10Z" : timestamp) | Some t -> t in + let last_drift_update = match last_drift_update with | None -> now | Some t -> t in let const_fee = match const_fee with | None -> (1000n, 1000n)| Some f -> f in // no default fee let pending_pool_updates = match pending_pool_updates with | None -> 0n | Some p -> p in let init_ovens = match init_ovens with | None -> (Big_map.empty : (oven_handle, oven) big_map) | Some o -> o in @@ -108,6 +130,10 @@ let init_contracts (alice_bal : nat option) (bob_bal : nat option) (init_lqt : n pendingPoolUpdates = 0n ; tokenAddress = Tezos.address (Test.to_contract typed_addr_fa12) ; lqtAddress = Tezos.address (Test.to_contract typed_addr_lqt) ; +#if ORACLE + lastOracleUpdate = now ; + consumerEntrypoint = Tezos.address (Test.to_entrypoint "cfmm_price" typed_addr_ctez : (nat * nat) contract) ; +#endif } in let (typed_addr_cfmm, program_cfmm, size_cfmm) = Test.originate main_cfmm cfmm_init_storage (1mutez * init_cash_pool) in @@ -138,6 +164,7 @@ let init_contracts (alice_bal : nat option) (bob_bal : nat option) (init_lqt : n (******** STANDARD SETUP ********) let test_setup = + // default init setup let (typed_addr_cfmm, typed_addr_ctez, typed_addr_fa12, typed_addr_lqt, addr_alice, addr_bob, addr_lqt, addr_dummy, addr_admin) = init_contracts @@ -166,12 +193,12 @@ let test_setup = cfmm_address = untyped_addr_cfmm; } in let actual_ctez_storage = Test.get_storage typed_addr_ctez in - - // assertions to verify storage is as expected // TODO : when Bitwise works, send storages to bytes and compare + // assertions to verify storage is as expected ( assert (expected_ctez_storage.cfmm_address = actual_ctez_storage.cfmm_address), assert (expected_ctez_storage.ctez_fa12_address = actual_ctez_storage.ctez_fa12_address) ) + // (* when Bytes works in test *) assert (Bytes.pack expected_ctez_storage = Bytes.pack actual_ctez_storage ) (******** DIFFERENCE EQUATIONS ********) @@ -214,15 +241,17 @@ let trade_cash_to_token_test (x, y, dx, target, rounds, const_fee : nat * nat * match (Big_map.find_opt addr_alice ctez_token_balances) with | None -> (failwith "Incomplete Cash to Token Transfer" : unit) | Some bal -> assert (bal = fee_num * (trade_dcash_for_dtoken x y dx target rounds) / fee_denom) + // (*debug mode*) | None -> (failwith "Incomplete Cash to Token Transfer" : nat * nat) + // (*debug mode*) | Some bal -> (bal, fee_num * (trade_dcash_for_dtoken x y dx target rounds) / fee_denom) let test_cash_to_token = let test_result = List.map trade_cash_to_token_test trade_params in () // if it reaches this, everything passed - + // (*debug mode*) test_result (** token to cash **) let trade_token_to_cash_test (x, y, dy, target, rounds, const_fee : nat * nat * nat * nat * int * (nat * nat)) = - let alice_initial_bal = dy in // in muctez; == 1_000 ctez + let alice_initial_bal = dy in // in muctez let (typed_addr_cfmm, typed_addr_ctez, typed_addr_fa12, typed_addr_lqt, addr_alice, addr_bob, addr_lqt, addr_dummy, addr_admin) = init_contracts @@ -232,7 +261,7 @@ let trade_token_to_cash_test (x, y, dy, target, rounds, const_fee : nat * nat * (None : nat option) (* init_total_supply *) (Some y : nat option) (* init_token_pool *) (Some x : nat option) (* init_cash_pool *) - (None : nat option) (* init_target *) + (Some target : nat option) (* init_target *) (None : int option) (* init_drift *) (None : timestamp option) (* last_drift_update *) (Some const_fee : (nat * nat) option) (* const_fee *) @@ -243,7 +272,6 @@ let trade_token_to_cash_test (x, y, dy, target, rounds, const_fee : nat * nat * let alice_source = Test.set_source addr_alice in let alice_txn_amt = alice_initial_bal in let addr_cfmm = Tezos.address (Test.to_contract typed_addr_cfmm) in - let alice_balance_old = Test.get_balance addr_alice in let addr_cfmm_balance_old = Test.get_balance addr_cfmm in let bob_balance_old = Test.get_balance addr_bob in @@ -277,12 +305,15 @@ let trade_token_to_cash_test (x, y, dy, target, rounds, const_fee : nat * nat * let bob_balance_delta = bob_balance / 1mutez - bob_balance_old / 1mutez in let (fee_num, fee_denom) = const_fee in assert (abs bob_balance_delta = fee_num * (trade_dtoken_for_dcash x y dy target rounds) / fee_denom) + // (*debug mode*) (abs bob_balance_delta, fee_num * (trade_dtoken_for_dcash x y dy target rounds) / fee_denom) let test_token_to_cash = let test_result = List.map trade_token_to_cash_test trade_params in () // if it reaches this, everything passed + //(*debug mode*) test_result - +(** test lists of each **) +let test_txn_lists = () (******** DIRECTIVES ********) let test_directives = () @@ -290,7 +321,55 @@ let test_directives = () (******** DRIFT ********) -let test_price = () + (* This is a minimal e.g., as timestamp arithmetic does not currently work in ligo test *) +// TODO : When time works, +// let test_drift (init_cash : nat) (init_token : nat) (init_target : nat) (time_delta : int) = +let test_drift (init_cash : nat) (init_token : nat) (init_target : nat) (now : timestamp) (later : timestamp) = + // some failing conditions + (* if (time_delta < 0) then (failwith "time_delta MUST BE NONNEGATIVE" : unit) else *) + + // init contracts + let (typed_addr_cfmm, typed_addr_ctez, typed_addr_fa12, typed_addr_lqt, + addr_alice, addr_bob, addr_lqt, addr_dummy, addr_admin) = + init_contracts + (None : nat option) (* alice_bal *) + (None : nat option) (* bob_bal *) + (None : nat option) (* init_lqt *) + (None : nat option) (* init_total_supply *) + (Some init_token : nat option) (* init_token_pool *) + (Some init_cash : nat option) (* init_cash_pool *) + (Some init_target : nat option) (* init_target *) + (None : int option) (* init_drift *) + (Some now : timestamp option) (* last_drift_update *) + (None : (nat * nat) option) (* const_fee *) + (None : nat option) (* pending_pool_updates *) + (None : (oven_handle, oven) big_map option) (* init_ovens *) + in + + + // update time + let time_has_elapsed = Test.set_now later in + + // calculate current price + let current_price = price_cash_to_token init_target init_cash init_token in + () (* + // call cfmm_price, updating the drift + // TODO : implicit account must trigger + let cfmm_source = Test.set_source addr_cfmm in + let txndata_cfmm_price = current_price in + let entrypoint_cfmm_price = + (Test.to_entrypoint "cfmm_price" typed_addr_ctez) in + let txn_cfmm_price = + (Test.transfer_to_contract_exn entrypoint_cfmm_price txndata_cfmm_price) in + + // check the drift + let ctez_storage = Test.get_storage typed_addr_ctez in + let actual_drift = ctez_storage.drift in + + // compute expected drift + let expected_drift = ... + + assert (actual_drift = expected_drift) *) // Checks that drift and target grew at expected rate after x mins // 5 mins, or 300 secs will be default // target should go up and down 5% in 0.1% increments \ No newline at end of file From c6658a48ff035c85b536d8b97ccaa51d9f6f9736 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Fri, 3 Sep 2021 13:11:59 +0100 Subject: [PATCH 50/92] test params update --- test_params.mligo | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test_params.mligo b/test_params.mligo index 0d49ed84..ebaa25be 100644 --- a/test_params.mligo +++ b/test_params.mligo @@ -1,6 +1,8 @@ -// params: x y dx target rounds OR y x dy target rounds +// params: x y dx target rounds *OR* y x dy target rounds +// params: cash token dcash target rounds *OR* token cash dtoken target rounds let trade_params : (nat * nat * nat * nat * int * (nat * nat)) list = [ - (1_000_000_000_000n, 1_000_000_000_000n, 100_000_000n, 1_000n, 4, (1_000n, 1_300n)) ; // defaults + (1_000_000_000_000n, 1_000_000_000_000n, 100_000_000n, (Bitwise.shift_left 1n 48n), 4, (1_000n, 1_000n)) ; // defaults + (1_000_000_000_000n, 10_000_000_000_000n, 100_000_000n, (Bitwise.shift_left 2n 48n), 4, (1_000n, 1_000n)) ; // TODO : populate ] From 905350a6b0279f7240514995aed3e5391debb22c Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Fri, 3 Sep 2021 13:45:59 +0100 Subject: [PATCH 51/92] Syntax standardization --- cfmm.mligo | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index 2ad9725b..ea7f7d3a 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -8,7 +8,7 @@ (* To support baking *) //#define HAS_BAKER (* To push prices to some consumer contract once per block *) -#define ORACLE +//#define ORACLE (* ============================================================================ @@ -98,7 +98,7 @@ type update_cash_pool_internal = update_fa12_pool type entrypoint = | AddLiquidity of add_liquidity | RemoveLiquidity of remove_liquidity -| Ctez_target of ctez_target +| CtezTarget of ctez_target | CashToToken of cash_to_token | TokenToCash of token_to_cash | TokenToToken of token_to_token @@ -800,6 +800,7 @@ let update_consumer (operations, storage : result) : result = then (operations, storage) else let consumer = match (Tezos.get_contract_opt storage.consumerEntrypoint : ((nat * nat) contract) option) with +// TODO : when ligo is fixed let consumer = match (Tezos.get_entrypoint_opt "cfmm_price" storage.consumerEntrypoint : ((nat * nat) contract) option) with | None -> (failwith error_CANNOT_GET_CFMM_PRICE_ENTRYPOINT_FROM_CONSUMER : (nat * nat) contract) | Some c -> c in ((Tezos.transaction (storage.cashPool, storage.tokenPool) 0mutez consumer) :: operations, @@ -816,7 +817,7 @@ let main ((entrypoint, storage) : entrypoint * storage) : result = add_liquidity param storage | RemoveLiquidity param -> remove_liquidity param storage - | Ctez_target param -> + | CtezTarget param -> ctez_target param storage #if HAS_BAKER | SetBaker param -> From 0b0a68bd7e29c35a67da3ba1121b8a65e3b6da64 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Fri, 3 Sep 2021 13:46:39 +0100 Subject: [PATCH 52/92] Each time the target is updated, it feeds it to the cfmm. --- ctez.mligo | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/ctez.mligo b/ctez.mligo index bb81ac12..6b2c9c85 100644 --- a/ctez.mligo +++ b/ctez.mligo @@ -57,7 +57,7 @@ type result = (operation list) * storage [@inline] let error_OVEN_NOT_UNDERCOLLATERALIZED = 11n [@inline] let error_EXCESSIVE_CTEZ_MINTING = 12n [@inline] let error_CALLER_MUST_BE_CFMM = 13n - +[@inline] let error_INVALID_CTEZ_TARGET_ENTRYPOINT = 14n #include "oven.mligo" @@ -213,7 +213,16 @@ let cfmm_price (storage : storage) (tez : nat) (token : nat) : result = else storage.drift - d_drift in - (([] : operation list), {storage with drift = drift ; last_drift_update = Tezos.now ; target = target}) + let cfmm_address = storage.cfmm_address in + let txndata_ctez_target = target in + let entrypoint_ctez_target = + (match (Tezos.get_contract_opt cfmm_address : nat contract option) with +// TODO : when ligo is fixed: (match (Tezos.get_entrypoint_opt "ctezTarget" cfmm_address : nat contract option) with + | None -> (failwith error_INVALID_CTEZ_TARGET_ENTRYPOINT : nat contract) + | Some c -> c ) in + let op_ctez_target = Tezos.transaction txndata_ctez_target 0tez entrypoint_ctez_target in + + ([op_ctez_target], {storage with drift = drift ; last_drift_update = Tezos.now ; target = target}) let main (p, s : parameter * storage) : result = match p with From aea9e189735ff8a10b31812caeac1ca00e739536 Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Fri, 3 Sep 2021 13:47:09 +0100 Subject: [PATCH 53/92] Adds test for drift --- test.mligo | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/test.mligo b/test.mligo index 2dea2859..4f48a674 100644 --- a/test.mligo +++ b/test.mligo @@ -220,6 +220,7 @@ let trade_cash_to_token_test (x, y, dx, target, rounds, const_fee : nat * nat * (None : nat option) (* pending_pool_updates *) (None : (oven_handle, oven) big_map option) (* init_ovens *) in + // get the TokenToCash entrypoint let alice_source = Test.set_source addr_alice in let trade_entrypoint : cash_to_token contract = @@ -234,10 +235,12 @@ let trade_cash_to_token_test (x, y, dx, target, rounds, const_fee : nat * nat * let alice_balance_old = Test.get_balance addr_alice in let alice_trade = (Test.transfer_to_contract_exn trade_entrypoint trade_data trade_amt) in + // alice should trade 500ctez for tez let ctez_fa12_storage = Test.get_storage typed_addr_fa12 in let ctez_token_balances = ctez_fa12_storage.tokens in let (fee_num, fee_denom) = const_fee in + match (Big_map.find_opt addr_alice ctez_token_balances) with | None -> (failwith "Incomplete Cash to Token Transfer" : unit) | Some bal -> assert (bal = fee_num * (trade_dcash_for_dtoken x y dx target rounds) / fee_denom) @@ -312,8 +315,6 @@ let test_token_to_cash = () // if it reaches this, everything passed //(*debug mode*) test_result -(** test lists of each **) -let test_txn_lists = () (******** DIRECTIVES ********) let test_directives = () @@ -324,6 +325,7 @@ let test_directives = () (* This is a minimal e.g., as timestamp arithmetic does not currently work in ligo test *) // TODO : When time works, // let test_drift (init_cash : nat) (init_token : nat) (init_target : nat) (time_delta : int) = +#if ORACLE let test_drift (init_cash : nat) (init_token : nat) (init_target : nat) (now : timestamp) (later : timestamp) = // some failing conditions (* if (time_delta < 0) then (failwith "time_delta MUST BE NONNEGATIVE" : unit) else *) @@ -346,30 +348,33 @@ let test_drift (init_cash : nat) (init_token : nat) (init_target : nat) (now : t (None : (oven_handle, oven) big_map option) (* init_ovens *) in - // update time let time_has_elapsed = Test.set_now later in - // calculate current price - let current_price = price_cash_to_token init_target init_cash init_token in - () (* - // call cfmm_price, updating the drift - // TODO : implicit account must trigger - let cfmm_source = Test.set_source addr_cfmm in - let txndata_cfmm_price = current_price in - let entrypoint_cfmm_price = - (Test.to_entrypoint "cfmm_price" typed_addr_ctez) in - let txn_cfmm_price = - (Test.transfer_to_contract_exn entrypoint_cfmm_price txndata_cfmm_price) in + // execute a transaction, which will trigger a drift update + let alice_source = Test.set_source addr_alice in + let trade_entrypoint : cash_to_token contract = + Test.to_entrypoint "cashToToken" typed_addr_cfmm in + let trade_amt = 100_000_000mutez in // could be anything + let trade_data : cash_to_token = { + to_ = addr_alice; + minTokensBought = 0n; + deadline = ("3000-01-01t10:10:10Z" : timestamp); + rounds = 4; + } in + let alice_trade = + (Test.transfer_to_contract_exn trade_entrypoint trade_data trade_amt) in // check the drift let ctez_storage = Test.get_storage typed_addr_ctez in let actual_drift = ctez_storage.drift in // compute expected drift - let expected_drift = ... + let expected_drift = 0n (* TODO: calculation here *) in + + assert (actual_drift = expected_drift) - assert (actual_drift = expected_drift) *) // Checks that drift and target grew at expected rate after x mins // 5 mins, or 300 secs will be default - // target should go up and down 5% in 0.1% increments \ No newline at end of file + // target should go up and down 5% in 0.1% increments +#endif From d964e1151fd2e8d5c897561dade7264e3a35290a Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Tue, 7 Sep 2021 16:30:37 +0100 Subject: [PATCH 54/92] Migrate tests to tests folder --- test.mligo => tests/test.mligo | 8 ++++---- test_params.mligo => tests/test_params.mligo | 0 unit_test.mligo => tests/unit_test.mligo | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) rename test.mligo => tests/test.mligo (99%) rename test_params.mligo => tests/test_params.mligo (100%) rename unit_test.mligo => tests/unit_test.mligo (93%) diff --git a/test.mligo b/tests/test.mligo similarity index 99% rename from test.mligo rename to tests/test.mligo index 4f48a674..ee7998b7 100644 --- a/test.mligo +++ b/tests/test.mligo @@ -4,7 +4,7 @@ * Contract Templates * ============================================================================= *) -#include "fa12.mligo" +#include "../fa12.mligo" let main_fa12 = main type fa12_storage = storage type fa12_parameter = parameter @@ -15,19 +15,19 @@ type lqt_storage = storage type lqt_parameter = parameter type lqt_result = result -#include "ctez.mligo" +#include "../ctez.mligo" let main_ctez = main type ctez_storage = storage type ctez_parameter = parameter type ctez_result = result -#include "cfmm.mligo" +#include "../cfmm.mligo" let main_cfmm = main type cfmm_storage = storage type cfmm_parameter = parameter type cfmm_result = result -#include "test_params.mligo" +#include "../test_params.mligo" (* ============================================================================= * Some Aux Functions diff --git a/test_params.mligo b/tests/test_params.mligo similarity index 100% rename from test_params.mligo rename to tests/test_params.mligo diff --git a/unit_test.mligo b/tests/unit_test.mligo similarity index 93% rename from unit_test.mligo rename to tests/unit_test.mligo index d27df237..ee88bd49 100644 --- a/unit_test.mligo +++ b/tests/unit_test.mligo @@ -4,7 +4,7 @@ * Contract Templates * ============================================================================= *) -#include "fa12.mligo" +#include "../fa12.mligo" let main_fa12 = main type fa12_storage = storage type fa12_parameter = parameter @@ -15,19 +15,19 @@ type lqt_storage = storage type lqt_parameter = parameter type lqt_result = result -#include "ctez.mligo" +#include "../ctez.mligo" let main_ctez = main type ctez_storage = storage type ctez_parameter = parameter type ctez_result = result -#include "cfmm.mligo" +#include "../cfmm.mligo" let main_cfmm = main type cfmm_storage = storage type cfmm_parameter = parameter type cfmm_result = result -#include "test_params.mligo" +#include "../test_params.mligo" (* ============================================================================= * Contract Templates From d4d3ac2ce1b2a0d3b09ac368681bfee6525e59dd Mon Sep 17 00:00:00 2001 From: Derek Sorensen Date: Wed, 8 Sep 2021 09:11:08 +0100 Subject: [PATCH 55/92] Remove fee from storage. --- cfmm.mligo | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index ea7f7d3a..37316902 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -1,5 +1,5 @@ (* Pick one of CASH_IS_TEZ, CASH_IS_FA2, CASH_IS_FA12. tokenToToken isn't supported for CASH_IS_FA12 *) -#define CASH_IS_TEZ +//#define CASH_IS_TEZ //#define CASH_IS_FA2 //#define CASH_IS_FA12 @@ -126,7 +126,6 @@ type storage = cashPool : nat ; lqtTotal : nat ; target : ctez_target ; - const_fee : nat * nat ; ctez_address : address ; pendingPoolUpdates : nat ; #if HAS_BAKER @@ -514,8 +513,7 @@ let cash_to_token (param : cash_to_token) (storage : storage) = let tokens_bought = // cash -> token calculation; *includes a fee* let bought = trade_dcash_for_dtoken cashPool storage.tokenPool cashSold storage.target rounds in - let (fee_num, fee_denom) = storage.const_fee in - let bought_after_fee = bought * fee_num / fee_denom in + let bought_after_fee = bought * const_fee / const_fee_denom in if bought_after_fee < minTokensBought then (failwith error_TOKENS_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_TOKENS_BOUGHT : nat) else @@ -561,8 +559,7 @@ let token_to_cash (param : token_to_cash) (storage : storage) = // token -> cash calculation; *includes a fee* let cash_bought = let bought = trade_dtoken_for_dcash storage.cashPool storage.tokenPool tokensSold storage.target rounds in - let (fee_num, fee_denom) = storage.const_fee in - let bought_after_fee = bought * fee_num / fee_denom in + let bought_after_fee = bought * const_fee / const_fee_denom in if bought_after_fee < minCashBought then (failwith error_CASH_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_CASH_BOUGHT : nat) else @@ -751,8 +748,7 @@ let token_to_token (param : token_to_token) (storage : storage) : result = (* We don't check that tokenPool > 0, because that is impossible unless all liquidity has been removed. *) let cash_bought = (let bought = trade_dtoken_for_dcash storage.cashPool storage.tokenPool tokensSold storage.target rounds in - let (fee_num, fee_denom) = storage.const_fee in - bought * fee_num / fee_denom) + bought * const_fee / const_fee_denom) in let new_cashPool = match is_nat (storage.cashPool - cash_bought) with | None -> (failwith error_CASH_POOL_MINUS_CASH_BOUGHT_IS_NEGATIVE : nat) From 33c7de42e4d7da89d3af21095ac3ef3af0ac8a4c Mon Sep 17 00:00:00 2001 From: Arthur Breitmam Date: Wed, 8 Sep 2021 11:53:08 +0100 Subject: [PATCH 56/92] cleanup the ctez cfmm by separating it from the general one allow tez to token to convert tez to ctez and then to token by routing through the tez/ctez contract --- cfmm.mligo | 188 ++--------------- cfmm_tez_ctez.mligo | 480 +++++++++++++++++++++++++++++++++++++++++- errors.mligo | 42 ++++ tests/unit_test.mligo | 2 +- 4 files changed, 537 insertions(+), 175 deletions(-) create mode 100644 errors.mligo diff --git a/cfmm.mligo b/cfmm.mligo index 37316902..bcbc6c8a 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -1,3 +1,5 @@ +#include "errors.mligo" + (* Pick one of CASH_IS_TEZ, CASH_IS_FA2, CASH_IS_FA12. tokenToToken isn't supported for CASH_IS_FA12 *) //#define CASH_IS_TEZ //#define CASH_IS_FA2 @@ -18,7 +20,7 @@ type add_liquidity = [@layout:comb] { owner : address ; (* address that will own the minted lqt *) - minLqtMinted : nat ; (* minimum number of lqt that must be minted *) + minLqtMinted : nat ; (* minimum number of lqt that must be minter *) maxTokensDeposited : nat ; (* maximum number of tokens that may be deposited *) #if !CASH_IS_TEZ cashDeposited : nat ; (* if cash isn't tez, specifiy the amount to be deposited *) @@ -43,7 +45,6 @@ type cash_to_token = cashSold : nat ; (* if cash isn't tez, how much cash is sought to be sold *) #endif deadline : timestamp ; (* time before which the request must be completed *) - rounds : int ; (* number of iterations in estimating the difference equations. Default should be 4n. *) } type token_to_cash = @@ -52,7 +53,6 @@ type token_to_cash = tokensSold : nat ; (* how many tokens are being sold *) minCashBought : nat ; (* minimum amount of cash desired *) deadline : timestamp ; (* time before which the request must be completed *) - rounds : int ; (* number of iterations in estimating the difference equations. Default should be 4n. *) } @@ -63,7 +63,6 @@ type token_to_token = [@annot:to] to_ : address ; (* where to send the output tokens *) tokensSold : nat ; (* amount of tokens to sell *) deadline : timestamp ; (* time before which the request must be completed *) - rounds : int ; (* number of iterations in estimating the difference equations. Default should be 4n. *) } #if HAS_BAKER @@ -74,9 +73,6 @@ type set_baker = } #endif -(* a such that (Bitwise.shift_right a 48n) is the target price from the ctez contract *) -type ctez_target = nat - (* getbalance update types for fa12 and fa2 *) type update_fa12_pool = nat type update_fa2_pool = ((address * nat) * nat) list @@ -98,7 +94,6 @@ type update_cash_pool_internal = update_fa12_pool type entrypoint = | AddLiquidity of add_liquidity | RemoveLiquidity of remove_liquidity -| CtezTarget of ctez_target | CashToToken of cash_to_token | TokenToCash of token_to_cash | TokenToToken of token_to_token @@ -125,8 +120,6 @@ type storage = { tokenPool : nat ; cashPool : nat ; lqtTotal : nat ; - target : ctez_target ; - ctez_address : address ; pendingPoolUpdates : nat ; #if HAS_BAKER freezeBaker : bool ; @@ -180,58 +173,6 @@ type mintOrBurn = { quantity : int ; target : address } -(* ============================================================================= - * Error codes - * ============================================================================= *) - -[@inline] let error_TOKEN_CONTRACT_MUST_HAVE_A_TRANSFER_ENTRYPOINT = 0n -[@inline] let error_ASSERTION_VIOLATED_CASH_BOUGHT_SHOULD_BE_LESS_THAN_CASHPOOL = 1n -[@inline] let error_PENDING_POOL_UPDATES_MUST_BE_ZERO = 2n -[@inline] let error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE = 3n -[@inline] let error_MAX_TOKENS_DEPOSITED_MUST_BE_GREATER_THAN_OR_EQUAL_TO_TOKENS_DEPOSITED = 4n -[@inline] let error_LQT_MINTED_MUST_BE_GREATER_THAN_MIN_LQT_MINTED = 5n -(* 6n *) -[@inline] let error_ONLY_NEW_MANAGER_CAN_ACCEPT = 7n -[@inline] let error_CASH_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_CASH_BOUGHT = 8n -[@inline] let error_INVALID_TO_ADDRESS = 9n -[@inline] let error_AMOUNT_MUST_BE_ZERO = 10n -[@inline] let error_THE_AMOUNT_OF_CASH_WITHDRAWN_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_CASH_WITHDRAWN = 11n -[@inline] let error_LQT_CONTRACT_MUST_HAVE_A_MINT_OR_BURN_ENTRYPOINT = 12n -[@inline] let error_THE_AMOUNT_OF_TOKENS_WITHDRAWN_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_TOKENS_WITHDRAWN = 13n -[@inline] let error_CANNOT_BURN_MORE_THAN_THE_TOTAL_AMOUNT_OF_LQT = 14n -[@inline] let error_TOKEN_POOL_MINUS_TOKENS_WITHDRAWN_IS_NEGATIVE = 15n -[@inline] let error_CASH_POOL_MINUS_CASH_WITHDRAWN_IS_NEGATIVE = 16n -[@inline] let error_CASH_POOL_MINUS_CASH_BOUGHT_IS_NEGATIVE = 17n -[@inline] let error_TOKENS_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_TOKENS_BOUGHT = 18n -[@inline] let error_TOKEN_POOL_MINUS_TOKENS_BOUGHT_IS_NEGATIVE = 19n -[@inline] let error_ONLY_MANAGER_CAN_SET_BAKER = 20n -[@inline] let error_ONLY_MANAGER_CAN_SET_MANAGER = 21n -[@inline] let error_BAKER_PERMANENTLY_FROZEN = 22n -[@inline] let error_LQT_ADDRESS_ALREADY_SET = 24n -[@inline] let error_CALL_NOT_FROM_AN_IMPLICIT_ACCOUNT = 25n -[@inline] let error_CALLER_MUST_BE_CTEZ = 26n -(* 27n *) -#if TOKEN_IS_FA2 -[@inline] let error_INVALID_FA2_TOKEN_CONTRACT_MISSING_BALANCE_OF = 28n -#else -[@inline] let error_INVALID_FA12_TOKEN_CONTRACT_MISSING_GETBALANCE = 28n -#endif -[@inline] let error_THIS_ENTRYPOINT_MAY_ONLY_BE_CALLED_BY_GETBALANCE_OF_TOKENADDRESS = 29n -[@inline] let error_INVALID_FA2_BALANCE_RESPONSE = 30n -[@inline] let error_INVALID_INTERMEDIATE_CONTRACT = 31n -#if !CASH_IS_TEZ -[@inline] let error_THIS_ENTRYPOINT_MAY_ONLY_BE_CALLED_BY_GETBALANCE_OF_CASHADDRESS = 30n -[@inline] let error_TEZ_DEPOSIT_WOULD_BE_BURNED = 32n -#if CASH_IS_FA2 -[@inline] let error_INVALID_FA2_CASH_CONTRACT_MISSING_GETBALANCE = 33n -#else -[@inline] let error_INVALID_FA12_CASH_CONTRACT_MISSING_GETBALANCE = 33n -[@inline] let error_MISSING_APPROVE_ENTRYPOINT_IN_CASH_CONTRACT = 34n -#endif -#endif -#if ORACLE -[@inline] let error_CANNOT_GET_CFMM_PRICE_ENTRYPOINT_FROM_CONSUMER = 35n -#endif (* ============================================================================= * Constants @@ -302,76 +243,6 @@ let cash_transfer (storage : storage) (from : address) (to_ : address) (cash_amo #endif #endif -(* Difference Equations *) -// The Isoutility Function in https://hackmd.io/MkPSYXDsTf-giBprcDrc3w -(* -// b is 2^48 below, implemented as bitwise shifts -let isoutility (target, cash, token : nat * nat * nat) : nat = - let x = cash in - let y = token in - let a = target in - let a2 = a * a in - let ax2 = a2 * x * x in - let by2 = Bitwise.shift_left (y * y) 96n in - (Bitwise.shift_right (abs((a * x * y) * (ax2 + by2) / (2 * a2))) 48n) - -// Returns the price dy/dx of the isoutility function, i.e. a map by multiplication ∆x => ∆y, at a given point (x,y) -let price_cash_to_token (target : nat) (cash : nat) (token : nat) : nat = - let (x,y) = (cash, token) in - let a = target in - let ax2 = x * x * a * a in - let by2 = Bitwise.shift_left (y * y) 96n in - let num = y * (3n * ax2 + by2) in - let denom = x * (ax2 + 3n * by2) in - num/denom -*) -// A function to transfer assets while maintaining a constant isoutility -let rec newton_dx_to_dy (x, y, dx, dy_approx, target, rounds : nat * nat * nat * nat * nat * int) : nat = - if (rounds <= 0) (* Newton generally converges in 4 rounds, so we bound computation there *) - then - dy_approx - else - let a = target in - let xp = x + dx in - let yp = y - dy_approx in - let ax2 = a * a * x * x in let by2 = Bitwise.shift_left (y * y) 96n in // (y * y) * b * b - let axp2 = a * a * xp * xp in let byp2 = Bitwise.shift_left (abs(yp * yp)) 96n in // (abs(yp * yp)) * b * b - (* Newton descent formula *) - let num = abs (xp * yp * (axp2 + byp2) - x * y * (ax2 + by2)) in // num is always positive, even without abs - let denom = xp * (axp2 + 3 * byp2) in - let adjust = num / denom in - let new_dy_approx = abs (dy_approx + adjust) in - let next_round = (rounds - 1) in - newton_dx_to_dy (x,y,dx,new_dy_approx,target,next_round) - (* - if denom = 0, then either: - 1. xp = 0 => x + dx = 0, which we don't allow, or - 2. a*xp = 0 and b*yp = 0 => a = 0 and (b = 0 or yp = 0), which implies - that the price target is 0. - *) - -// A function that outputs dy (diff_token) given x, y, and dx -let trade_dcash_for_dtoken (x : nat) (y : nat) (dx : nat) (target : nat) (rounds : int) : nat = - let dy_approx = 0n in // start at 0n to always be an underestimate - - if (y - dy_approx <= 0) - then - (failwith error_TOKEN_POOL_MINUS_TOKENS_WITHDRAWN_IS_NEGATIVE : nat) - else - newton_dx_to_dy (x, y, dx, dy_approx, target, rounds) - -// A function that outputs dx (diff_cash) given target, x, y, and dy -let trade_dtoken_for_dcash (x : nat) (y : nat) (dy : nat) (target : nat) (rounds : int) : nat = - let dx_approx = 0n in // start at 0n to always be an underestimate - let target_inv = (Bitwise.shift_left 1n 96n) / target in // b^2 / a - (* Will get error if a = 0 to begin with, but if that's the case we have bigger fish to fry *) - - if (x - dx_approx <= 0) - then - (failwith error_CASH_POOL_MINUS_CASH_WITHDRAWN_IS_NEGATIVE : nat) - else - newton_dx_to_dy (y, x, dy, dx_approx, target_inv, rounds) - (* ============================================================================= * Entrypoint Functions * ============================================================================= *) @@ -481,14 +352,6 @@ let remove_liquidity (param : remove_liquidity) (storage : storage) : result = end end -let ctez_target (param : ctez_target) (storage : storage) = - if Tezos.sender <> storage.ctez_address then - (failwith error_CALLER_MUST_BE_CTEZ : result) - else - let updated_target = param in - let storage = {storage with target = updated_target} in - (([] : operation list), storage) - let cash_to_token (param : cash_to_token) (storage : storage) = let { to_ = to_ ; @@ -496,8 +359,7 @@ let cash_to_token (param : cash_to_token) (storage : storage) = #if !CASH_IS_TEZ cashSold = cashSold ; #endif - deadline = deadline ; - rounds = rounds } = param in + deadline = deadline } = param in #if CASH_IS_TEZ let cashSold = mutez_to_natural Tezos.amount in @@ -511,13 +373,11 @@ let cash_to_token (param : cash_to_token) (storage : storage) = unless all liquidity has been removed. *) let cashPool = storage.cashPool in let tokens_bought = - // cash -> token calculation; *includes a fee* - let bought = trade_dcash_for_dtoken cashPool storage.tokenPool cashSold storage.target rounds in - let bought_after_fee = bought * const_fee / const_fee_denom in - if bought_after_fee < minTokensBought then + (let bought = (cashSold * const_fee * storage.tokenPool) / (cashPool * const_fee_denom + (cashSold * const_fee)) in + if bought < minTokensBought then (failwith error_TOKENS_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_TOKENS_BOUGHT : nat) else - bought_after_fee + bought) in let new_tokenPool = (match is_nat (storage.tokenPool - tokens_bought) with | None -> (failwith error_TOKEN_POOL_MINUS_TOKENS_BOUGHT_IS_NEGATIVE : nat) @@ -544,8 +404,7 @@ let token_to_cash (param : token_to_cash) (storage : storage) = let { to_ = to_ ; tokensSold = tokensSold ; minCashBought = minCashBought ; - deadline = deadline ; - rounds = rounds } = param in + deadline = deadline } = param in if storage.pendingPoolUpdates > 0n then (failwith error_PENDING_POOL_UPDATES_MUST_BE_ZERO : result) @@ -556,21 +415,15 @@ let token_to_cash (param : token_to_cash) (storage : storage) = else (* We don't check that tokenPool > 0, because that is impossible unless all liquidity has been removed. *) - // token -> cash calculation; *includes a fee* let cash_bought = - let bought = trade_dtoken_for_dcash storage.cashPool storage.tokenPool tokensSold storage.target rounds in - let bought_after_fee = bought * const_fee / const_fee_denom in - if bought_after_fee < minCashBought then - (failwith error_CASH_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_CASH_BOUGHT : nat) - else - bought_after_fee - in + let bought = ((tokensSold * const_fee * storage.cashPool) / (storage.tokenPool * const_fee_denom + (tokensSold * const_fee))) in + if bought < minCashBought then (failwith error_CASH_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_CASH_BOUGHT : nat) else bought in let op_token = token_transfer storage Tezos.sender Tezos.self_address tokensSold in #if CASH_IS_TEZ - let op_cash = cash_transfer to_ cash_bought in + let op_cash = cash_transfer to_ cash_bought in #else - let op_cash = cash_transfer storage Tezos.self_address to_ cash_bought in + let op_cash = cash_transfer storage Tezos.self_address to_ cash_bought in #endif let new_cashPool = match is_nat (storage.cashPool - cash_bought) with | None -> (failwith error_ASSERTION_VIOLATED_CASH_BOUGHT_SHOULD_BE_LESS_THAN_CASHPOOL : nat) @@ -730,8 +583,7 @@ let token_to_token (param : token_to_token) (storage : storage) : result = minTokensBought = minTokensBought ; to_ = to_ ; tokensSold = tokensSold ; - deadline = deadline ; - rounds = rounds } = param in + deadline = deadline } = param in let outputCfmmContract_contract: cash_to_token contract = (match (Tezos.get_entrypoint_opt "%cashToToken" outputCfmmContract : cash_to_token contract option) with @@ -746,10 +598,7 @@ let token_to_token (param : token_to_token) (storage : storage) : result = (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) else (* We don't check that tokenPool > 0, because that is impossible unless all liquidity has been removed. *) - let cash_bought = - (let bought = trade_dtoken_for_dcash storage.cashPool storage.tokenPool tokensSold storage.target rounds in - bought * const_fee / const_fee_denom) - in + let cash_bought = ((tokensSold * const_fee * storage.cashPool) / (storage.tokenPool * const_fee_denom + (tokensSold * const_fee))) in let new_cashPool = match is_nat (storage.cashPool - cash_bought) with | None -> (failwith error_CASH_POOL_MINUS_CASH_BOUGHT_IS_NEGATIVE : nat) | Some n -> n in @@ -758,7 +607,7 @@ let token_to_token (param : token_to_token) (storage : storage) : result = #if CASH_IS_TEZ let op_send_cash_to_output = Tezos.transaction { minTokensBought = minTokensBought ; - deadline = deadline; to_ = to_ ; rounds = rounds} + deadline = deadline; to_ = to_ } (natural_to_mutez cash_bought) outputCfmmContract_contract in #else @@ -778,7 +627,7 @@ let token_to_token (param : token_to_token) (storage : storage) : result = #endif let op_send_cash_to_output = Tezos.transaction { minTokensBought = minTokensBought ; cashSold = cash_bought ; - deadline = deadline ; to_ = to_ ; rounds = rounds} + deadline = deadline ; to_ = to_} 0mutez outputCfmmContract_contract in #endif @@ -796,7 +645,6 @@ let update_consumer (operations, storage : result) : result = then (operations, storage) else let consumer = match (Tezos.get_contract_opt storage.consumerEntrypoint : ((nat * nat) contract) option) with -// TODO : when ligo is fixed let consumer = match (Tezos.get_entrypoint_opt "cfmm_price" storage.consumerEntrypoint : ((nat * nat) contract) option) with | None -> (failwith error_CANNOT_GET_CFMM_PRICE_ENTRYPOINT_FROM_CONSUMER : (nat * nat) contract) | Some c -> c in ((Tezos.transaction (storage.cashPool, storage.tokenPool) 0mutez consumer) :: operations, @@ -813,8 +661,6 @@ let main ((entrypoint, storage) : entrypoint * storage) : result = add_liquidity param storage | RemoveLiquidity param -> remove_liquidity param storage - | CtezTarget param -> - ctez_target param storage #if HAS_BAKER | SetBaker param -> set_baker param storage @@ -849,4 +695,4 @@ let main ((entrypoint, storage) : entrypoint * storage) : result = | UpdateTokenPoolInternal token_pool -> update_token_pool_internal token_pool storage | SetLqtAddress param -> - set_lqt_address param storage + set_lqt_address param storage \ No newline at end of file diff --git a/cfmm_tez_ctez.mligo b/cfmm_tez_ctez.mligo index 83088093..eb165a2d 100644 --- a/cfmm_tez_ctez.mligo +++ b/cfmm_tez_ctez.mligo @@ -1,5 +1,479 @@ -#define ORACLE -#define CASH_IS_TEZ +#include "errors.mligo" + +[@inline] let error_CALLER_MUST_BE_CTEZ = 1000n +[@inline] let error_ASSERTION_VIOLATED_TEZ_BOUGHT_SHOULD_BE_LESS_THAN_TEZPOOL = 1001n + +(* ============================================================================ + * Entrypoints + * ============================================================================ *) + +type add_liquidity = + [@layout:comb] + { owner : address ; (* address that will own the minted lqt *) + minLqtMinted : nat ; (* minimum number of lqt that must be minted *) + maxCashDeposited : nat ; (* maximum number of cash that may be deposited *) + deadline : timestamp ; (* time before which the request must be completed *) + } + +type remove_liquidity = + [@layout:comb] + { [@annot:to] to_ : address ; (* recipient of the liquidity redemption *) + lqtBurned : nat ; (* amount of lqt owned by sender to burn *) + minTezWithdrawn : nat ; (* minimum amount of tez to withdraw *) + minCashWithdrawn : nat ; (* minimum amount of cash to withdraw *) + deadline : timestamp ; (* time before which the request must be completed *) + } + +type tez_to_cash = + [@layout:comb] + { [@annot:to] to_ : address ; (* where to send the cash *) + minCashBought : nat ; (* minimum amount of cash that must be bought *) + deadline : timestamp ; (* time before which the request must be completed *) + rounds : int ; (* number of iterations in estimating the difference equations. Default should be 4n. *) + } + +type cash_to_tez = + [@layout:comb] + { [@annot:to] to_ : address ; (* where to send the tez *) + cashSold : nat ; (* how many cash are being sold *) + minTezBought : nat ; (* minimum amount of tez desired *) + deadline : timestamp ; (* time before which the request must be completed *) + rounds : int ; (* number of iterations in estimating the difference equations. Default should be 4n. *) + } + +type cash_to_token = + [@layout:comb] + { [@annot:to] to_ : address ; (* where to send the tokens *) + minTokensBought : nat ; (* minimum amount of tokens that must be bought *) + cashSold : nat ; (* if cash isn't tez, how much cash is sought to be sold *) + deadline : timestamp ; (* time before which the request must be completed *) + } + +type tez_to_token = + [@layout:comb] + { outputCfmmContract : address ; (* other cfmm contract *) + minTokensBought : nat ; (* minimum amount of cash bought *) + [@annot:to] to_ : address ; (* where to send the output cash *) + tezSold : nat ; (* amount of tez to sell *) + deadline : timestamp ; (* time before which the request must be completed *) + rounds : int ; (* number of iterations in estimating the difference equations. Default should be 4n. *) + } + + +(* A target t such that t / 2^48 is the target price from the ctez contract *) +type ctez_target = nat + + +type entrypoint = +| AddLiquidity of add_liquidity +| RemoveLiquidity of remove_liquidity +| CtezTarget of ctez_target +| TezToCash of tez_to_cash +| CashToTez of cash_to_tez +| TezToToken of tez_to_token +| SetLqtAddress of address + + +(* ============================================================================= + * Storage + * ============================================================================= *) + +type storage = + [@layout:comb] + { cashPool : nat ; + tezPool : nat ; + lqtTotal : nat ; + target : ctez_target ; + ctez_address : address ; + cashAddress : address ; + lqtAddress : address ; + lastOracleUpdate : timestamp ; + consumerEntrypoint : address ; + } + +(* Type Synonyms *) + +type result = operation list * storage + +(* FA2 *) +type cash_id = nat +type balance_of = ((address * cash_id) list * ((((address * nat) * nat) list) contract)) +(* FA1.2 *) +type get_balance = address * (nat contract) + +type cash_contract_transfer = address * (address * nat) + +(* custom entrypoint for LQT FA1.2 *) +type mintOrBurn = + [@layout:comb] + { quantity : int ; + target : address } + + + + + +(* ============================================================================= + * Constants + * ============================================================================= *) + +[@inline] let null_address = ("tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" : address) [@inline] let const_fee = 9995n (* 0.05% fee *) [@inline] let const_fee_denom = 10000n -#include "cfmm.mligo" \ No newline at end of file + +(* ============================================================================= + * Functions + * ============================================================================= *) + +(* this is slightly inefficient to inline, but, nice to have a clean stack for + the entrypoints for the Coq verification *) +[@inline] +let mutez_to_natural (a: tez) : nat = a / 1mutez + +[@inline] +let natural_to_mutez (a: nat): tez = a * 1mutez + +[@inline] +let is_a_nat (i : int) : nat option = Michelson.is_nat i + +let ceildiv (numerator : nat) (denominator : nat) : nat = abs ((- numerator) / (int denominator)) + +[@inline] +let mint_or_burn (storage : storage) (target : address) (quantity : int) : operation = + (* Returns an operation that mints or burn lqt from the lqt FA1.2 contract. A negative quantity + corresponds to a burn, a positive one to a mint. *) + let lqt_admin : mintOrBurn contract = + match (Tezos.get_entrypoint_opt "%mintOrBurn" storage.lqtAddress : mintOrBurn contract option) with + | None -> (failwith error_LQT_CONTRACT_MUST_HAVE_A_MINT_OR_BURN_ENTRYPOINT : mintOrBurn contract) + | Some contract -> contract in + Tezos.transaction {quantity = quantity ; target = target} 0mutez lqt_admin + +[@inline] +let cash_transfer (storage : storage) (from : address) (to_ : address) (cash_amount : nat) : operation = + (* Returns an operation that transfers cash between from and to. *) + let cash_contract: cash_contract_transfer contract = + match (Tezos.get_entrypoint_opt "%transfer" storage.cashAddress : cash_contract_transfer contract option) with + | None -> (failwith error_CASH_CONTRACT_MUST_HAVE_A_TRANSFER_ENTRYPOINT : cash_contract_transfer contract) + | Some contract -> contract in + Tezos.transaction (from, (to_, cash_amount)) 0mutez cash_contract + +[@inline] +let tez_transfer (to_ : address) (tez_amount : nat) : operation= + (* Tez transfer operation, in the case where TEZ_IS_TEZ *) + let to_contract : unit contract = + match (Tezos.get_contract_opt to_ : unit contract option) with + | None -> (failwith error_INVALID_TO_ADDRESS : unit contract) + | Some c -> c in + Tezos.transaction () (natural_to_mutez tez_amount) to_contract + +(* A function to transfer assets while maintaining a constant* isoutility. + * ... or slightly increasing due to loss of precision or incomplete convergence *) +let rec newton_dx_to_dy (x, y, dx, dy_approx, rounds : nat * nat * nat * nat * int) : nat = + if rounds <= 0 then + dy_approx + else + let xp = x + dx in + let yp = y - dy_approx in + let x2 = x * x in + let y2 = y * y in + let xp2 = xp * xp in + let yp2 = abs (yp * yp) in + + (* Newton descent formula *) + (* num is always positive, even without abs which is only there for casting to nat *) + let num = abs (xp * yp * (xp2 + yp2) - x * y * (x2 + y2)) in + let denom = xp * (xp2 + 3n * yp2) in + let adjust = num / denom in + let new_dy_approx = dy_approx + adjust in + newton_dx_to_dy (x, y, dx, new_dy_approx, rounds - 1) + (* + if denom = 0, then either: + 1. xp = 0 => x + dx = 0, which we don't allow, or + 2. xp = 0 and yp = 0 => a = 0 and (b = 0 or yp = 0), which implies + that the price target is 0. + *) + +// A function that outputs dy (diff_cash) given x, y, and dx +let trade_dtez_for_dcash (tez : nat) (cash : nat) (dtez : nat) (target : nat) (rounds : int) : nat = + let x = target * tez in + let y = Bitwise.shift_left cash 48n in + let dx = target * dtez in + let dy_approx = newton_dx_to_dy (x, y, dx, 0n, rounds) in + let dcash_approx = Bitwise.shift_left dy_approx 48n in + if (cash - dcash_approx <= 0) then + (failwith error_CASH_POOL_MINUS_CASH_WITHDRAWN_IS_NEGATIVE : nat) + else + dcash_approx + + +// A function that outputs dx (diff_tez) given target, x, y, and dy +let trade_dcash_for_dtez (tez : nat) (cash : nat) (dcash : nat) (target : nat) (rounds : int) : nat = + let x = target * cash in + let y = Bitwise.shift_left tez 48n in + let dx = target * dcash in + let dy_approx = newton_dx_to_dy (x, y, dx, 0n, rounds) in + let dtez_approx = dy_approx / target in + if (tez - dtez_approx <= 0) then + (failwith error_TEZ_POOL_MINUS_TEZ_WITHDRAWN_IS_NEGATIVE : nat) (* should never happen *) + else + dtez_approx + +(* ============================================================================= + * Entrypoint Functions + * ============================================================================= *) + +(* We assume the contract is originated with at least one liquidity + * provider set up already, so lqtTotal, cashPool and tezPool will + * always be positive after the initial setup, unless all liquidity is + * removed, at which point the contract is considered dead and stops working + * properly. If this is a concern, at least one address should keep at least a + * very small amount of liquidity in the contract forever. *) + +let add_liquidity (param : add_liquidity) (storage: storage) : result = + (* Adds liquidity to the contract, mints lqt in exchange for the deposited liquidity. *) + let { + owner = owner ; + minLqtMinted = minLqtMinted ; + maxCashDeposited = maxCashDeposited ; + deadline = deadline } = param in + let tezDeposited = mutez_to_natural Tezos.amount in + if Tezos.now >= deadline then + (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) + else + (* The contract is initialized, use the existing exchange rate + mints nothing if the contract has been emptied, but that's OK *) + let tezPool : nat = storage.tezPool in + let lqt_minted : nat = tezDeposited * storage.lqtTotal / tezPool in + let cash_deposited : nat = ceildiv (tezDeposited * storage.cashPool) tezPool in + + if cash_deposited > maxCashDeposited then + (failwith error_MAX_CASH_DEPOSITED_MUST_BE_GREATER_THAN_OR_EQUAL_TO_CASH_DEPOSITED : result) + else if lqt_minted < minLqtMinted then + (failwith error_LQT_MINTED_MUST_BE_GREATER_THAN_MIN_LQT_MINTED : result) + else + let storage = {storage with + lqtTotal = storage.lqtTotal + lqt_minted ; + cashPool = storage.cashPool + cash_deposited ; + tezPool = storage.tezPool + tezDeposited} in + + (* send cash from sender to self *) + let op_cash = cash_transfer storage Tezos.sender Tezos.self_address cash_deposited in + (* mint lqt cash for them *) + let op_lqt = mint_or_burn storage owner (int lqt_minted) in + ([op_cash; op_lqt], storage) + +let remove_liquidity (param : remove_liquidity) (storage : storage) : result = + (* Removes liquidity to the contract by burning lqt. *) + let { to_ = to_ ; + lqtBurned = lqtBurned ; + minTezWithdrawn = minTezWithdrawn ; + minCashWithdrawn = minCashWithdrawn ; + deadline = deadline } = param in + + if Tezos.now >= deadline then + (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) + else if Tezos.amount > 0mutez then + (failwith error_AMOUNT_MUST_BE_ZERO : result) + else begin + let tez_withdrawn : nat = (lqtBurned * storage.tezPool) / storage.lqtTotal in + let cash_withdrawn : nat = (lqtBurned * storage.cashPool) / storage.lqtTotal in + + (* Check that minimum withdrawal conditions are met *) + if tez_withdrawn < minTezWithdrawn then + (failwith error_THE_AMOUNT_OF_TEZ_WITHDRAWN_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_TEZ_WITHDRAWN : result) + else if cash_withdrawn < minCashWithdrawn then + (failwith error_THE_AMOUNT_OF_CASH_WITHDRAWN_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_CASH_WITHDRAWN : result) + (* Proceed to form the operations and update the storage *) + else begin + (* calculate lqtTotal, convert int to nat *) + let new_lqtTotal = match (is_a_nat ( storage.lqtTotal - lqtBurned)) with + (* This check should be unecessary, the fa12 logic normally takes care of it *) + | None -> (failwith error_CANNOT_BURN_MORE_THAN_THE_TOTAL_AMOUNT_OF_LQT : nat) + | Some n -> n in + (* Calculate cashPool, convert int to nat *) + let new_cashPool = match is_a_nat (storage.cashPool - cash_withdrawn) with + | None -> (failwith error_CASH_POOL_MINUS_CASH_WITHDRAWN_IS_NEGATIVE : nat) + | Some n -> n in + let new_tezPool = match is_nat (storage.tezPool - tez_withdrawn) with + | None -> (failwith error_TEZ_POOL_MINUS_TEZ_WITHDRAWN_IS_NEGATIVE : nat) + | Some n -> n in + let op_lqt = mint_or_burn storage Tezos.sender (0 - lqtBurned) in + let op_cash = cash_transfer storage Tezos.self_address Tezos.sender cash_withdrawn in + let op_tez = tez_transfer to_ tez_withdrawn in + let storage = {storage with tezPool = new_tezPool ; lqtTotal = new_lqtTotal ; cashPool = new_cashPool} in + ([op_lqt; op_cash; op_tez], storage) + end + end + +let ctez_target (param : ctez_target) (storage : storage) = + if Tezos.sender <> storage.ctez_address then + (failwith error_CALLER_MUST_BE_CTEZ : result) + else + let updated_target = param in + let storage = {storage with target = updated_target} in + (([] : operation list), storage) + + +let tez_to_cash (param : tez_to_cash) (storage : storage) = + let { to_ = to_ ; + minCashBought = minCashBought ; + deadline = deadline ; + rounds = rounds } = param in + let tezSold = mutez_to_natural Tezos.amount in + if Tezos.now >= deadline then + (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) + else begin + (* We don't check that tezPool > 0, because that is impossible + unless all liquidity has been removed. *) + let tezPool = storage.tezPool in + let cash_bought = + // tez -> cash calculation; *includes a fee* + let bought = trade_dtez_for_dcash tezPool storage.cashPool tezSold storage.target rounds in + let bought_after_fee = bought * const_fee / const_fee_denom in + if bought_after_fee < minCashBought then + (failwith error_CASH_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_CASH_BOUGHT : nat) + else + bought_after_fee + in + let new_cashPool = (match is_nat (storage.cashPool - cash_bought) with + | None -> (failwith error_CASH_POOL_MINUS_CASH_BOUGHT_IS_NEGATIVE : nat) + | Some difference -> difference) in + + (* Update tezPool. *) + let storage = { storage with tezPool = storage.tezPool + tezSold ; cashPool = new_cashPool } in + (* Send tez from sender to self. *) + (* Send cash_withdrawn from exchange to sender. *) + let op_cash = cash_transfer storage Tezos.self_address to_ cash_bought in + ([op_cash], storage) + end + + +let cash_to_tez (param : cash_to_tez) (storage : storage) = + (* Accepts a payment in cash and sends tez. *) + let { to_ = to_ ; + cashSold = cashSold ; + minTezBought = minTezBought ; + deadline = deadline ; + rounds = rounds } = param in + + + if Tezos.now >= deadline then + (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) + else if Tezos.amount > 0mutez then + (failwith error_AMOUNT_MUST_BE_ZERO : result) + else + (* We don't check that cashPool > 0, because that is impossible + unless all liquidity has been removed. *) + // cash -> tez calculation; *includes a fee* + let tez_bought = + let bought = trade_dcash_for_dtez storage.tezPool storage.cashPool cashSold storage.target rounds in + let bought_after_fee = bought * const_fee / const_fee_denom in + if bought_after_fee < minTezBought then + (failwith error_TEZ_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_TEZ_BOUGHT : nat) + else + bought_after_fee + in + + let op_cash = cash_transfer storage Tezos.sender Tezos.self_address cashSold in + let op_tez = tez_transfer to_ tez_bought in + let new_tezPool = match is_nat (storage.tezPool - tez_bought) with + | None -> (failwith error_ASSERTION_VIOLATED_TEZ_BOUGHT_SHOULD_BE_LESS_THAN_TEZPOOL : nat) + | Some n -> n in + let storage = {storage with cashPool = storage.cashPool + cashSold ; + tezPool = new_tezPool} in + ([op_cash; op_tez], storage) + + +let default_ (storage : storage) : result = +(* Entrypoint to allow depositing tez. *) + (* update tezPool *) + let storage = {storage with tezPool = storage.tezPool + mutez_to_natural Tezos.amount} in (([] : operation list), storage) + + +let set_lqt_address (lqtAddress : address) (storage : storage) : result = + if Tezos.amount > 0mutez then + (failwith error_AMOUNT_MUST_BE_ZERO : result) + else if storage.lqtAddress <> null_address then + (failwith error_LQT_ADDRESS_ALREADY_SET : result) + else + (([] : operation list), {storage with lqtAddress = lqtAddress}) + + +let tez_to_token (param : tez_to_token) (storage : storage) : result = + let { outputCfmmContract = outputCfmmContract ; + minTokensBought = minTokensBought ; + to_ = to_ ; + tezSold = tezSold ; + deadline = deadline ; + rounds = rounds } = param in + + let outputCfmmContract_contract: cash_to_token contract = + (match (Tezos.get_entrypoint_opt "%cashToToken" outputCfmmContract : cash_to_token contract option) with + | None -> (failwith error_INVALID_INTERMEDIATE_CONTRACT : cash_to_token contract) + | Some c -> c) in + + if Tezos.now >= deadline then + (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) + else + (* We don't check that cashPool > 0, because that is impossible unless all liquidity has been removed. *) + let cash_bought = + (let bought = trade_dtez_for_dcash storage.tezPool storage.cashPool tezSold storage.target rounds in + bought * const_fee / const_fee_denom) + in + let new_cashPool = match is_nat (storage.cashPool - cash_bought) with + | None -> (failwith error_CASH_POOL_MINUS_CASH_BOUGHT_IS_NEGATIVE : nat) + | Some n -> n in + let storage = {storage with tezPool = storage.tezPool + tezSold ; + cashPool = new_cashPool } in + let allow_output_to_withdraw_cash = + let cashContract_approve = (match (Tezos.get_entrypoint_opt "%approve" storage.cashAddress : (address * nat) contract option) with + | None -> (failwith error_MISSING_APPROVE_ENTRYPOINT_IN_CASH_CONTRACT : (address * nat) contract) + | Some c -> c) in + (Tezos.transaction (outputCfmmContract, 0n) + 0mutez + cashContract_approve, + Tezos.transaction (outputCfmmContract, cash_bought) + 0mutez + cashContract_approve) in + let op_send_cash_to_output = Tezos.transaction { minTokensBought = minTokensBought ; + cashSold = cash_bought ; + deadline = deadline ; to_ = to_} + 0mutez + outputCfmmContract_contract in + ([allow_output_to_withdraw_cash.0 ; allow_output_to_withdraw_cash.1 ; op_send_cash_to_output] , storage) + + +let update_consumer (operations, storage : result) : result = + if storage.lastOracleUpdate = Tezos.now + then (operations, storage) + else + let consumer = match (Tezos.get_contract_opt storage.consumerEntrypoint : ((nat * nat) contract) option) with +// TODO : when ligo is fixed let consumer = match (Tezos.get_entrypoint_opt "cfmm_price" storage.consumerEntrypoint : ((nat * nat) contract) option) with + | None -> (failwith error_CANNOT_GET_CFMM_PRICE_ENTRYPOINT_FROM_CONSUMER : (nat * nat) contract) + | Some c -> c in + ((Tezos.transaction (storage.tezPool, storage.cashPool) 0mutez consumer) :: operations, + {storage with lastOracleUpdate = Tezos.now}) + +(* ============================================================================= + * Main + * ============================================================================= *) + +let main ((entrypoint, storage) : entrypoint * storage) : result = + match entrypoint with + | AddLiquidity param -> + add_liquidity param storage + | RemoveLiquidity param -> + remove_liquidity param storage + | CtezTarget param -> + ctez_target param storage + | TezToCash param -> + update_consumer + (tez_to_cash param storage) + | CashToTez param -> + update_consumer + (cash_to_tez param storage) + | TezToToken param -> + update_consumer + (tez_to_token param storage) + | SetLqtAddress param -> + set_lqt_address param storage diff --git a/errors.mligo b/errors.mligo new file mode 100644 index 00000000..62773065 --- /dev/null +++ b/errors.mligo @@ -0,0 +1,42 @@ +(* ============================================================================= + * Error codes + * ============================================================================= *) + +[@inline] let error_TOKEN_CONTRACT_MUST_HAVE_A_TRANSFER_ENTRYPOINT = 0n +[@inline] let error_ASSERTION_VIOLATED_CASH_BOUGHT_SHOULD_BE_LESS_THAN_CASHPOOL = 1n +[@inline] let error_PENDING_POOL_UPDATES_MUST_BE_ZERO = 2n +[@inline] let error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE = 3n +[@inline] let error_MAX_TOKENS_DEPOSITED_MUST_BE_GREATER_THAN_OR_EQUAL_TO_TOKENS_DEPOSITED = 4n +[@inline] let error_LQT_MINTED_MUST_BE_GREATER_THAN_MIN_LQT_MINTED = 5n +[@inline] let error_MAX_CASH_DEPOSITED_MUST_BE_GREATER_THAN_OR_EQUAL_TO_CASH_DEPOSITED = 6n +[@inline] let error_ONLY_NEW_MANAGER_CAN_ACCEPT = 7n +[@inline] let error_CASH_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_CASH_BOUGHT = 8n +[@inline] let error_INVALID_TO_ADDRESS = 9n +[@inline] let error_AMOUNT_MUST_BE_ZERO = 10n +[@inline] let error_THE_AMOUNT_OF_CASH_WITHDRAWN_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_CASH_WITHDRAWN = 11n +[@inline] let error_LQT_CONTRACT_MUST_HAVE_A_MINT_OR_BURN_ENTRYPOINT = 12n +[@inline] let error_THE_AMOUNT_OF_TOKENS_WITHDRAWN_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_TOKENS_WITHDRAWN = 13n +[@inline] let error_CANNOT_BURN_MORE_THAN_THE_TOTAL_AMOUNT_OF_LQT = 14n +[@inline] let error_TOKEN_POOL_MINUS_TOKENS_WITHDRAWN_IS_NEGATIVE = 15n +[@inline] let error_CASH_POOL_MINUS_CASH_WITHDRAWN_IS_NEGATIVE = 16n +[@inline] let error_CASH_POOL_MINUS_CASH_BOUGHT_IS_NEGATIVE = 17n +[@inline] let error_TOKENS_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_TOKENS_BOUGHT = 18n +[@inline] let error_TOKEN_POOL_MINUS_TOKENS_BOUGHT_IS_NEGATIVE = 19n +[@inline] let error_ONLY_MANAGER_CAN_SET_BAKER = 20n +[@inline] let error_ONLY_MANAGER_CAN_SET_MANAGER = 21n +[@inline] let error_BAKER_PERMANENTLY_FROZEN = 22n +[@inline] let error_LQT_ADDRESS_ALREADY_SET = 24n +[@inline] let error_CALL_NOT_FROM_AN_IMPLICIT_ACCOUNT = 25n +[@inline] let error_CASH_CONTRACT_MUST_HAVE_A_TRANSFER_ENTRYPOINT = 26n +[@inline] let error_TEZ_POOL_MINUS_TEZ_WITHDRAWN_IS_NEGATIVE = 27n +[@inline] let error_INVALID_FA12_TOKEN_CONTRACT_MISSING_GETBALANCE = 28n +[@inline] let error_THIS_ENTRYPOINT_MAY_ONLY_BE_CALLED_BY_GETBALANCE_OF_TOKENADDRESS = 29n +[@inline] let error_INVALID_FA2_BALANCE_RESPONSE = 30n +[@inline] let error_INVALID_INTERMEDIATE_CONTRACT = 31n +[@inline] let error_THIS_ENTRYPOINT_MAY_ONLY_BE_CALLED_BY_GETBALANCE_OF_CASHADDRESS = 30n +[@inline] let error_TEZ_DEPOSIT_WOULD_BE_BURNED = 32n +[@inline] let error_INVALID_FA12_CASH_CONTRACT_MISSING_GETBALANCE = 33n +[@inline] let error_MISSING_APPROVE_ENTRYPOINT_IN_CASH_CONTRACT = 34n +[@inline] let error_CANNOT_GET_CFMM_PRICE_ENTRYPOINT_FROM_CONSUMER = 35n +[@inline] let error_THE_AMOUNT_OF_TEZ_WITHDRAWN_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_TEZ_WITHDRAWN = 36n +[@inline] let error_TEZ_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_TEZ_BOUGHT = 37n diff --git a/tests/unit_test.mligo b/tests/unit_test.mligo index ee88bd49..35daf7f5 100644 --- a/tests/unit_test.mligo +++ b/tests/unit_test.mligo @@ -27,7 +27,7 @@ type cfmm_storage = storage type cfmm_parameter = parameter type cfmm_result = result -#include "../test_params.mligo" +#include "test_params.mligo" (* ============================================================================= * Contract Templates From 0861a4834cafca329416bba205ab36265f667605 Mon Sep 17 00:00:00 2001 From: Arthur Breitmam Date: Wed, 8 Sep 2021 13:01:14 +0100 Subject: [PATCH 57/92] remove tez / baker option for cfmm --- cfmm.mligo | 146 ++--------------------------------------------------- 1 file changed, 5 insertions(+), 141 deletions(-) diff --git a/cfmm.mligo b/cfmm.mligo index bcbc6c8a..21336948 100644 --- a/cfmm.mligo +++ b/cfmm.mligo @@ -1,14 +1,12 @@ #include "errors.mligo" -(* Pick one of CASH_IS_TEZ, CASH_IS_FA2, CASH_IS_FA12. tokenToToken isn't supported for CASH_IS_FA12 *) -//#define CASH_IS_TEZ +(* Pick one of CASH_IS_FA2, CASH_IS_FA12. tokenToToken isn't supported for CASH_IS_FA2 *) //#define CASH_IS_FA2 //#define CASH_IS_FA12 (* If the token uses the fa2 standard *) //#define TOKEN_IS_FA2 -(* To support baking *) -//#define HAS_BAKER + (* To push prices to some consumer contract once per block *) //#define ORACLE @@ -22,9 +20,7 @@ type add_liquidity = { owner : address ; (* address that will own the minted lqt *) minLqtMinted : nat ; (* minimum number of lqt that must be minter *) maxTokensDeposited : nat ; (* maximum number of tokens that may be deposited *) -#if !CASH_IS_TEZ cashDeposited : nat ; (* if cash isn't tez, specifiy the amount to be deposited *) -#endif deadline : timestamp ; (* time before which the request must be completed *) } @@ -41,9 +37,7 @@ type cash_to_token = [@layout:comb] { [@annot:to] to_ : address ; (* where to send the tokens *) minTokensBought : nat ; (* minimum amount of tokens that must be bought *) -#if !CASH_IS_TEZ cashSold : nat ; (* if cash isn't tez, how much cash is sought to be sold *) -#endif deadline : timestamp ; (* time before which the request must be completed *) } @@ -65,13 +59,6 @@ type token_to_token = deadline : timestamp ; (* time before which the request must be completed *) } -#if HAS_BAKER -type set_baker = - [@layout:comb] - { baker : key_hash option ; (* delegate address, None if undelegated *) - freezeBaker : bool ; (* whether to permanently freeze the baker *) - } -#endif (* getbalance update types for fa12 and fa2 *) type update_fa12_pool = nat @@ -99,15 +86,7 @@ type entrypoint = | TokenToToken of token_to_token | UpdatePools of unit | UpdateTokenPoolInternal of update_token_pool_internal -#if HAS_BAKER -| SetBaker of set_baker -| SetManager of address -| AcceptManager of unit -| Default of unit -#endif -#if !CASH_IS_TEZ | UpdateCashPoolInternal of update_cash_pool_internal -#endif | SetLqtAddress of address @@ -121,18 +100,11 @@ type storage = cashPool : nat ; lqtTotal : nat ; pendingPoolUpdates : nat ; -#if HAS_BAKER - freezeBaker : bool ; - manager : address ; - new_manager : address ; -#endif tokenAddress : address ; #if TOKEN_IS_FA2 tokenId : nat ; #endif -#if !CASH_IS_TEZ cashAddress : address ; -#endif #if CASH_IS_FA2 cashId : nat ; #endif @@ -221,15 +193,7 @@ let token_transfer (storage : storage) (from : address) (to_ : address) (token_a #endif [@inline] -#if CASH_IS_TEZ -let cash_transfer (to_ : address) (cash_amount : nat) : operation= - (* Cash transfer operation, in the case where CASH_IS_TEZ *) - let to_contract : unit contract = - match (Tezos.get_contract_opt to_ : unit contract option) with - | None -> (failwith error_INVALID_TO_ADDRESS : unit contract) - | Some c -> c in - Tezos.transaction () (natural_to_mutez cash_amount) to_contract -#else + let cash_transfer (storage : storage) (from : address) (to_ : address) (cash_amount : nat) : operation= (* Cash transfer operation, in the case where cash is some fa2 or fa12 token *) let cash_contract: cash_contract_transfer contract = @@ -241,7 +205,6 @@ let cash_transfer (storage : storage) (from : address) (to_ : address) (cash_amo #else Tezos.transaction (from, (to_, cash_amount)) 0mutez cash_contract #endif -#endif (* ============================================================================= * Entrypoint Functions @@ -260,13 +223,8 @@ let add_liquidity (param : add_liquidity) (storage: storage) : result = owner = owner ; minLqtMinted = minLqtMinted ; maxTokensDeposited = maxTokensDeposited ; -#if !CASH_IS_TEZ cashDeposited = cashDeposited ; -#endif deadline = deadline } = param in -#if CASH_IS_TEZ - let cashDeposited = mutez_to_natural Tezos.amount in -#endif if storage.pendingPoolUpdates > 0n then (failwith error_PENDING_POOL_UPDATES_MUST_BE_ZERO : result) else if Tezos.now >= deadline then @@ -290,17 +248,13 @@ let add_liquidity (param : add_liquidity) (storage: storage) : result = (* send tokens from sender to self *) let op_token = token_transfer storage Tezos.sender Tezos.self_address tokens_deposited in -#if !CASH_IS_TEZ (* send cash from sender to self *) let op_cash = cash_transfer storage Tezos.sender Tezos.self_address cashDeposited in -#endif (* mint lqt tokens for them *) let op_lqt = mint_or_burn storage owner (int lqt_minted) in ([op_token; -#if !CASH_IS_TEZ op_cash; -#endif op_lqt], storage) let remove_liquidity (param : remove_liquidity) (storage : storage) : result = @@ -342,11 +296,7 @@ let remove_liquidity (param : remove_liquidity) (storage : storage) : result = | Some n -> n in let op_lqt = mint_or_burn storage Tezos.sender (0 - lqtBurned) in let op_token = token_transfer storage Tezos.self_address Tezos.sender tokens_withdrawn in -#if CASH_IS_TEZ - let op_cash = cash_transfer to_ cash_withdrawn in -#else let op_cash = cash_transfer storage Tezos.self_address to_ cash_withdrawn in -#endif let storage = {storage with cashPool = new_cashPool ; lqtTotal = new_lqtTotal ; tokenPool = new_tokenPool} in ([op_lqt; op_token; op_cash], storage) end @@ -356,14 +306,9 @@ let remove_liquidity (param : remove_liquidity) (storage : storage) : result = let cash_to_token (param : cash_to_token) (storage : storage) = let { to_ = to_ ; minTokensBought = minTokensBought ; -#if !CASH_IS_TEZ cashSold = cashSold ; -#endif deadline = deadline } = param in -#if CASH_IS_TEZ - let cashSold = mutez_to_natural Tezos.amount in -#endif if storage.pendingPoolUpdates > 0n then (failwith error_PENDING_POOL_UPDATES_MUST_BE_ZERO : result) else if Tezos.now >= deadline then @@ -386,16 +331,10 @@ let cash_to_token (param : cash_to_token) (storage : storage) = (* Update cashPool. *) let storage = { storage with cashPool = storage.cashPool + cashSold ; tokenPool = new_tokenPool } in (* Send cash from sender to self. *) -#if !CASH_IS_TEZ let op_cash = cash_transfer storage Tezos.sender Tezos.self_address cashSold in -#endif (* Send tokens_withdrawn from exchange to sender. *) let op_token = token_transfer storage Tezos.self_address to_ tokens_bought in - ([ -#if !CASH_IS_TEZ - op_cash; -#endif - op_token], storage) + ([op_cash; op_token], storage) end @@ -420,11 +359,7 @@ let token_to_cash (param : token_to_cash) (storage : storage) = if bought < minCashBought then (failwith error_CASH_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_CASH_BOUGHT : nat) else bought in let op_token = token_transfer storage Tezos.sender Tezos.self_address tokensSold in -#if CASH_IS_TEZ - let op_cash = cash_transfer to_ cash_bought in -#else let op_cash = cash_transfer storage Tezos.self_address to_ cash_bought in -#endif let new_cashPool = match is_nat (storage.cashPool - cash_bought) with | None -> (failwith error_ASSERTION_VIOLATED_CASH_BOUGHT_SHOULD_BE_LESS_THAN_CASHPOOL : nat) | Some n -> n in @@ -435,51 +370,8 @@ let token_to_cash (param : token_to_cash) (storage : storage) = let default_ (storage : storage) : result = (* Entrypoint to allow depositing tez. *) -#if CASH_IS_TEZ - (* update cashPool *) - if storage.pendingPoolUpdates > 0n then - (failwith error_PENDING_POOL_UPDATES_MUST_BE_ZERO: result) - else - let storage = {storage with cashPool = storage.cashPool + mutez_to_natural Tezos.amount } in - (([] : operation list), storage) -#else (failwith error_TEZ_DEPOSIT_WOULD_BE_BURNED : result) -#endif - -#if HAS_BAKER -let set_baker (param : set_baker) (storage : storage) : result = - let { baker = baker ; - freezeBaker = freezeBaker } = param in - if storage.pendingPoolUpdates > 0n then - (failwith error_PENDING_POOL_UPDATES_MUST_BE_ZERO : result) - else if Tezos.amount > 0mutez then - (failwith error_AMOUNT_MUST_BE_ZERO : result) - else if Tezos.sender <> storage.manager then - (failwith error_ONLY_MANAGER_CAN_SET_BAKER : result) - else if storage.freezeBaker then - (failwith error_BAKER_PERMANENTLY_FROZEN : result) - else - ([ Tezos.set_delegate baker ], {storage with freezeBaker = freezeBaker}) -let set_manager (new_manager : address) (storage : storage) : result = - if storage.pendingPoolUpdates > 0n then - (failwith error_PENDING_POOL_UPDATES_MUST_BE_ZERO : result) - else if Tezos.amount > 0mutez then - (failwith error_AMOUNT_MUST_BE_ZERO : result) - else if Tezos.sender <> storage.manager then - (failwith error_ONLY_MANAGER_CAN_SET_MANAGER : result) - else - (([] : operation list), {storage with new_manager = new_manager}) - -let accept_manager (storage : storage) : result = - if storage.pendingPoolUpdates > 0n then - (failwith error_PENDING_POOL_UPDATES_MUST_BE_ZERO : result) - else if Tezos.sender <> storage.new_manager then - (failwith error_ONLY_NEW_MANAGER_CAN_ACCEPT : result) - else - (([] : operation list), {storage with manager = storage.new_manager}) - -#endif let set_lqt_address (lqtAddress : address) (storage : storage) : result = if storage.pendingPoolUpdates > 0n then @@ -499,9 +391,7 @@ let update_pools (storage : storage) : result = (failwith error_AMOUNT_MUST_BE_ZERO : result) else let cfmm_update_token_pool_internal : update_token_pool_internal contract = Tezos.self "%updateTokenPoolInternal" in -#if !CASH_IS_TEZ let cfmm_update_cash_pool_internal : update_cash_pool_internal contract = Tezos.self "%updateCashPoolInternal" in -#endif #if TOKEN_IS_FA2 let token_balance_of : balance_of contract = (match (Tezos.get_entrypoint_opt "%balance_of" storage.tokenAddress : balance_of contract option) with @@ -532,11 +422,7 @@ let update_pools (storage : storage) : result = let op_cash = Tezos.transaction ([(Tezos.self_address, storage.cashId)], cfmm_update_cash_pool_internal) 0mutez cash_balance_of in let op_list = op_cash :: op_list in #endif -#if CASH_IS_TEZ - let pendingPoolUpdates = 1n in -#else let pendingPoolUpdates = 2n in -#endif (op_list, {storage with pendingPoolUpdates = pendingPoolUpdates}) @@ -564,7 +450,6 @@ let update_token_pool_internal (pool_update : update_token_pool_internal) (stora let pendingPoolUpdates = abs (storage.pendingPoolUpdates - 1n) in (([] : operation list), {storage with tokenPool = pool ; pendingPoolUpdates = pendingPoolUpdates}) -#if !CASH_IS_TEZ let update_cash_pool_internal (pool_update : update_cash_pool_internal) (storage : storage) : result = if (storage.pendingPoolUpdates = 0n or Tezos.sender <> storage.cashAddress) then (failwith error_THIS_ENTRYPOINT_MAY_ONLY_BE_CALLED_BY_GETBALANCE_OF_CASHADDRESS : result) @@ -576,7 +461,6 @@ let update_cash_pool_internal (pool_update : update_cash_pool_internal) (storage #endif let pendingPoolUpdates = abs (storage.pendingPoolUpdates - 1) in (([] : operation list), {storage with cashPool = pool ; pendingPoolUpdates = pendingPoolUpdates}) -#endif let token_to_token (param : token_to_token) (storage : storage) : result = let { outputCfmmContract = outputCfmmContract ; @@ -605,12 +489,6 @@ let token_to_token (param : token_to_token) (storage : storage) : result = let storage = {storage with tokenPool = storage.tokenPool + tokensSold ; cashPool = new_cashPool } in -#if CASH_IS_TEZ - let op_send_cash_to_output = Tezos.transaction { minTokensBought = minTokensBought ; - deadline = deadline; to_ = to_ } - (natural_to_mutez cash_bought) - outputCfmmContract_contract in -#else let allow_output_to_withdraw_cash = #if CASH_IS_FA12 let cashContract_approve = (match (Tezos.get_entrypoint_opt "%approve" storage.cashAddress : (address * nat) contract option) with @@ -630,12 +508,10 @@ let token_to_token (param : token_to_token) (storage : storage) : result = deadline = deadline ; to_ = to_} 0mutez outputCfmmContract_contract in -#endif let op_accept_token_from_sender = token_transfer storage Tezos.sender Tezos.self_address tokensSold in ([ -#if !CASH_IS_TEZ + allow_output_to_withdraw_cash.0 ; allow_output_to_withdraw_cash.1 ; -#endif op_send_cash_to_output; op_accept_token_from_sender] , storage) @@ -661,20 +537,8 @@ let main ((entrypoint, storage) : entrypoint * storage) : result = add_liquidity param storage | RemoveLiquidity param -> remove_liquidity param storage -#if HAS_BAKER - | SetBaker param -> - set_baker param storage - | SetManager param -> - set_manager param storage - | AcceptManager -> - accept_manager storage - | Default -> - default_ storage -#endif -#if !CASH_IS_TEZ | UpdateCashPoolInternal cash_pool -> update_cash_pool_internal cash_pool storage -#endif | UpdatePools -> update_pools storage | CashToToken param -> From 1dd464093b88d003c9fc57ca9601c90715978e80 Mon Sep 17 00:00:00 2001 From: Arthur Breitmam Date: Thu, 9 Sep 2021 10:03:37 +0100 Subject: [PATCH 58/92] fix division by 2^48 --- cfmm_tez_ctez.mligo | 62 ++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/cfmm_tez_ctez.mligo b/cfmm_tez_ctez.mligo index eb165a2d..7f3ea576 100644 --- a/cfmm_tez_ctez.mligo +++ b/cfmm_tez_ctez.mligo @@ -84,7 +84,7 @@ type storage = tezPool : nat ; lqtTotal : nat ; target : ctez_target ; - ctez_address : address ; + ctez_address : address ; cashAddress : address ; lqtAddress : address ; lastOracleUpdate : timestamp ; @@ -166,22 +166,22 @@ let tez_transfer (to_ : address) (tez_amount : nat) : operation= | Some c -> c in Tezos.transaction () (natural_to_mutez tez_amount) to_contract -(* A function to transfer assets while maintaining a constant* isoutility. +(* A function to transfer assets while maintaining a constant* isoutility. * ... or slightly increasing due to loss of precision or incomplete convergence *) -let rec newton_dx_to_dy (x, y, dx, dy_approx, rounds : nat * nat * nat * nat * int) : nat = - if rounds <= 0 then +let rec newton_dx_to_dy (x, y, dx, dy_approx, rounds : nat * nat * nat * nat * int) : nat = + if rounds <= 0 then dy_approx - else + else let xp = x + dx in - let yp = y - dy_approx in + let yp = y - dy_approx in let x2 = x * x in - let y2 = y * y in + let y2 = y * y in let xp2 = xp * xp in let yp2 = abs (yp * yp) in (* Newton descent formula *) (* num is always positive, even without abs which is only there for casting to nat *) - let num = abs (xp * yp * (xp2 + yp2) - x * y * (x2 + y2)) in + let num = abs (xp * yp * (xp2 + yp2) - x * y * (x2 + y2)) in let denom = xp * (xp2 + 3n * yp2) in let adjust = num / denom in let new_dy_approx = dy_approx + adjust in @@ -194,12 +194,12 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, rounds : nat * nat * nat * nat * i *) // A function that outputs dy (diff_cash) given x, y, and dx -let trade_dtez_for_dcash (tez : nat) (cash : nat) (dtez : nat) (target : nat) (rounds : int) : nat = +let trade_dtez_for_dcash (tez : nat) (cash : nat) (dtez : nat) (target : nat) (rounds : int) : nat = let x = target * tez in let y = Bitwise.shift_left cash 48n in - let dx = target * dtez in + let dx = target * dtez in let dy_approx = newton_dx_to_dy (x, y, dx, 0n, rounds) in - let dcash_approx = Bitwise.shift_left dy_approx 48n in + let dcash_approx = Bitwise.shift_right dy_approx 48n in if (cash - dcash_approx <= 0) then (failwith error_CASH_POOL_MINUS_CASH_WITHDRAWN_IS_NEGATIVE : nat) else @@ -207,12 +207,12 @@ let trade_dtez_for_dcash (tez : nat) (cash : nat) (dtez : nat) (target : nat) (r // A function that outputs dx (diff_tez) given target, x, y, and dy -let trade_dcash_for_dtez (tez : nat) (cash : nat) (dcash : nat) (target : nat) (rounds : int) : nat = +let trade_dcash_for_dtez (tez : nat) (cash : nat) (dcash : nat) (target : nat) (rounds : int) : nat = let x = target * cash in let y = Bitwise.shift_left tez 48n in - let dx = target * dcash in + let dx = target * dcash in let dy_approx = newton_dx_to_dy (x, y, dx, 0n, rounds) in - let dtez_approx = dy_approx / target in + let dtez_approx = dy_approx / target in if (tez - dtez_approx <= 0) then (failwith error_TEZ_POOL_MINUS_TEZ_WITHDRAWN_IS_NEGATIVE : nat) (* should never happen *) else @@ -236,7 +236,7 @@ let add_liquidity (param : add_liquidity) (storage: storage) : result = minLqtMinted = minLqtMinted ; maxCashDeposited = maxCashDeposited ; deadline = deadline } = param in - let tezDeposited = mutez_to_natural Tezos.amount in + let tezDeposited = mutez_to_natural Tezos.amount in if Tezos.now >= deadline then (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) else @@ -269,7 +269,7 @@ let remove_liquidity (param : remove_liquidity) (storage : storage) : result = minTezWithdrawn = minTezWithdrawn ; minCashWithdrawn = minCashWithdrawn ; deadline = deadline } = param in - + if Tezos.now >= deadline then (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) else if Tezos.amount > 0mutez then @@ -305,11 +305,11 @@ let remove_liquidity (param : remove_liquidity) (storage : storage) : result = end end -let ctez_target (param : ctez_target) (storage : storage) = +let ctez_target (param : ctez_target) (storage : storage) = if Tezos.sender <> storage.ctez_address then (failwith error_CALLER_MUST_BE_CTEZ : result) else - let updated_target = param in + let updated_target = param in let storage = {storage with target = updated_target} in (([] : operation list), storage) @@ -319,7 +319,7 @@ let tez_to_cash (param : tez_to_cash) (storage : storage) = minCashBought = minCashBought ; deadline = deadline ; rounds = rounds } = param in - let tezSold = mutez_to_natural Tezos.amount in + let tezSold = mutez_to_natural Tezos.amount in if Tezos.now >= deadline then (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) else begin @@ -355,8 +355,8 @@ let cash_to_tez (param : cash_to_tez) (storage : storage) = minTezBought = minTezBought ; deadline = deadline ; rounds = rounds } = param in - - + + if Tezos.now >= deadline then (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) else if Tezos.amount > 0mutez then @@ -368,9 +368,9 @@ let cash_to_tez (param : cash_to_tez) (storage : storage) = let tez_bought = let bought = trade_dcash_for_dtez storage.tezPool storage.cashPool cashSold storage.target rounds in let bought_after_fee = bought * const_fee / const_fee_denom in - if bought_after_fee < minTezBought then - (failwith error_TEZ_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_TEZ_BOUGHT : nat) - else + if bought_after_fee < minTezBought then + (failwith error_TEZ_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_TEZ_BOUGHT : nat) + else bought_after_fee in @@ -386,11 +386,11 @@ let cash_to_tez (param : cash_to_tez) (storage : storage) = let default_ (storage : storage) : result = (* Entrypoint to allow depositing tez. *) - (* update tezPool *) + (* update tezPool *) let storage = {storage with tezPool = storage.tezPool + mutez_to_natural Tezos.amount} in (([] : operation list), storage) -let set_lqt_address (lqtAddress : address) (storage : storage) : result = +let set_lqt_address (lqtAddress : address) (storage : storage) : result = if Tezos.amount > 0mutez then (failwith error_AMOUNT_MUST_BE_ZERO : result) else if storage.lqtAddress <> null_address then @@ -411,12 +411,12 @@ let tez_to_token (param : tez_to_token) (storage : storage) : result = (match (Tezos.get_entrypoint_opt "%cashToToken" outputCfmmContract : cash_to_token contract option) with | None -> (failwith error_INVALID_INTERMEDIATE_CONTRACT : cash_to_token contract) | Some c -> c) in - + if Tezos.now >= deadline then (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) else (* We don't check that cashPool > 0, because that is impossible unless all liquidity has been removed. *) - let cash_bought = + let cash_bought = (let bought = trade_dtez_for_dcash storage.tezPool storage.cashPool tezSold storage.target rounds in bought * const_fee / const_fee_denom) in @@ -439,9 +439,9 @@ let tez_to_token (param : tez_to_token) (storage : storage) : result = cashSold = cash_bought ; deadline = deadline ; to_ = to_} 0mutez - outputCfmmContract_contract in + outputCfmmContract_contract in ([allow_output_to_withdraw_cash.0 ; allow_output_to_withdraw_cash.1 ; op_send_cash_to_output] , storage) - + let update_consumer (operations, storage : result) : result = if storage.lastOracleUpdate = Tezos.now @@ -474,6 +474,6 @@ let main ((entrypoint, storage) : entrypoint * storage) : result = (cash_to_tez param storage) | TezToToken param -> update_consumer - (tez_to_token param storage) + (tez_to_token param storage) | SetLqtAddress param -> set_lqt_address param storage From 9e25cd03ffd6a3618d0385d163fd5927a3d0a29e Mon Sep 17 00:00:00 2001 From: Arthur Breitmam Date: Thu, 9 Sep 2021 10:13:36 +0100 Subject: [PATCH 59/92] optimize newton for gas --- cfmm_tez_ctez.mligo | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/cfmm_tez_ctez.mligo b/cfmm_tez_ctez.mligo index 7f3ea576..4d59fa3f 100644 --- a/cfmm_tez_ctez.mligo +++ b/cfmm_tez_ctez.mligo @@ -168,24 +168,20 @@ let tez_transfer (to_ : address) (tez_amount : nat) : operation= (* A function to transfer assets while maintaining a constant* isoutility. * ... or slightly increasing due to loss of precision or incomplete convergence *) -let rec newton_dx_to_dy (x, y, dx, dy_approx, rounds : nat * nat * nat * nat * int) : nat = + +let rec newton_dx_to_dy_rec (xp, xp2, x3y_plus_y3x, y, dy_approx, rounds : nat * nat * nat * nat * nat * int) : nat = if rounds <= 0 then dy_approx else - let xp = x + dx in let yp = y - dy_approx in - let x2 = x * x in - let y2 = y * y in - let xp2 = xp * xp in let yp2 = abs (yp * yp) in - (* Newton descent formula *) (* num is always positive, even without abs which is only there for casting to nat *) - let num = abs (xp * yp * (xp2 + yp2) - x * y * (x2 + y2)) in + let num = abs (xp * yp * (xp2 + yp2) - x3y_plus_y3x) in let denom = xp * (xp2 + 3n * yp2) in let adjust = num / denom in let new_dy_approx = dy_approx + adjust in - newton_dx_to_dy (x, y, dx, new_dy_approx, rounds - 1) + newton_dx_to_dy_rec (xp, xp2, x3y_plus_y3x, y, new_dy_approx, rounds - 1) (* if denom = 0, then either: 1. xp = 0 => x + dx = 0, which we don't allow, or @@ -193,6 +189,13 @@ let rec newton_dx_to_dy (x, y, dx, dy_approx, rounds : nat * nat * nat * nat * i that the price target is 0. *) +let rec newton_dx_to_dy (x, y, dx, dy_approx, rounds : nat * nat * nat * nat * int) : nat = + let xp = x + dx in + let xp2 = xp * xp in + let x3y_plus_y3x = x * y * (x * x + y * y) in + newton_dx_to_dy_rec (xp, xp2, x3y_plus_y3x, y, dy_approx, rounds) + + // A function that outputs dy (diff_cash) given x, y, and dx let trade_dtez_for_dcash (tez : nat) (cash : nat) (dtez : nat) (target : nat) (rounds : int) : nat = let x = target * tez in From b3ed41d269e27a6e34c694e78c56dbcf6f52034f Mon Sep 17 00:00:00 2001 From: Arthur Breitmam Date: Thu, 9 Sep 2021 10:57:32 +0100 Subject: [PATCH 60/92] simplify newton function call --- cfmm_tez_ctez.mligo | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cfmm_tez_ctez.mligo b/cfmm_tez_ctez.mligo index 4d59fa3f..1c040ba3 100644 --- a/cfmm_tez_ctez.mligo +++ b/cfmm_tez_ctez.mligo @@ -189,11 +189,11 @@ let rec newton_dx_to_dy_rec (xp, xp2, x3y_plus_y3x, y, dy_approx, rounds : nat * that the price target is 0. *) -let rec newton_dx_to_dy (x, y, dx, dy_approx, rounds : nat * nat * nat * nat * int) : nat = +let rec newton_dx_to_dy (x, y, dx, rounds : nat * nat * nat * int) : nat = let xp = x + dx in let xp2 = xp * xp in let x3y_plus_y3x = x * y * (x * x + y * y) in - newton_dx_to_dy_rec (xp, xp2, x3y_plus_y3x, y, dy_approx, rounds) + newton_dx_to_dy_rec (xp, xp2, x3y_plus_y3x, y, 0n, rounds) // A function that outputs dy (diff_cash) given x, y, and dx @@ -201,7 +201,7 @@ let trade_dtez_for_dcash (tez : nat) (cash : nat) (dtez : nat) (target : nat) (r let x = target * tez in let y = Bitwise.shift_left cash 48n in let dx = target * dtez in - let dy_approx = newton_dx_to_dy (x, y, dx, 0n, rounds) in + let dy_approx = newton_dx_to_dy (x, y, dx, rounds) in let dcash_approx = Bitwise.shift_right dy_approx 48n in if (cash - dcash_approx <= 0) then (failwith error_CASH_POOL_MINUS_CASH_WITHDRAWN_IS_NEGATIVE : nat) @@ -214,7 +214,7 @@ let trade_dcash_for_dtez (tez : nat) (cash : nat) (dcash : nat) (target : nat) ( let x = target * cash in let y = Bitwise.shift_left tez 48n in let dx = target * dcash in - let dy_approx = newton_dx_to_dy (x, y, dx, 0n, rounds) in + let dy_approx = newton_dx_to_dy (x, y, dx, rounds) in let dtez_approx = dy_approx / target in if (tez - dtez_approx <= 0) then (failwith error_TEZ_POOL_MINUS_TEZ_WITHDRAWN_IS_NEGATIVE : nat) (* should never happen *) From f6cffc87b778c8c0a0e365265b14d261247e7009 Mon Sep 17 00:00:00 2001 From: Arthur Breitmam Date: Thu, 9 Sep 2021 13:37:47 +0100 Subject: [PATCH 61/92] fix inverted price --- cfmm_tez_ctez.mligo | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cfmm_tez_ctez.mligo b/cfmm_tez_ctez.mligo index 1c040ba3..d592e133 100644 --- a/cfmm_tez_ctez.mligo +++ b/cfmm_tez_ctez.mligo @@ -198,11 +198,11 @@ let rec newton_dx_to_dy (x, y, dx, rounds : nat * nat * nat * int) : nat = // A function that outputs dy (diff_cash) given x, y, and dx let trade_dtez_for_dcash (tez : nat) (cash : nat) (dtez : nat) (target : nat) (rounds : int) : nat = - let x = target * tez in - let y = Bitwise.shift_left cash 48n in - let dx = target * dtez in + let x = Bitwise.shift_left tez 48n in + let y = target * cash in + let dx = Bitwise.shift_left dtez 48n in let dy_approx = newton_dx_to_dy (x, y, dx, rounds) in - let dcash_approx = Bitwise.shift_right dy_approx 48n in + let dcash_approx = dy_approx / target in if (cash - dcash_approx <= 0) then (failwith error_CASH_POOL_MINUS_CASH_WITHDRAWN_IS_NEGATIVE : nat) else @@ -215,7 +215,7 @@ let trade_dcash_for_dtez (tez : nat) (cash : nat) (dcash : nat) (target : nat) ( let y = Bitwise.shift_left tez 48n in let dx = target * dcash in let dy_approx = newton_dx_to_dy (x, y, dx, rounds) in - let dtez_approx = dy_approx / target in + let dtez_approx = Bitwise.shift_right dy_approx 48n in if (tez - dtez_approx <= 0) then (failwith error_TEZ_POOL_MINUS_TEZ_WITHDRAWN_IS_NEGATIVE : nat) (* should never happen *) else From 7ecaddad1965eda6694030573c6a3615593551b8 Mon Sep 17 00:00:00 2001 From: Arthur Breitmam Date: Mon, 13 Sep 2021 09:39:37 +0100 Subject: [PATCH 62/92] fix price oracle reporting --- cfmm_tez_ctez.mligo | 9 ++++++++- ctez.mligo | 25 ++++++++----------------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/cfmm_tez_ctez.mligo b/cfmm_tez_ctez.mligo index d592e133..cff733ea 100644 --- a/cfmm_tez_ctez.mligo +++ b/cfmm_tez_ctez.mligo @@ -221,6 +221,13 @@ let trade_dcash_for_dtez (tez : nat) (cash : nat) (dcash : nat) (target : nat) ( else dtez_approx +let marginal_price (tez : nat) (cash : nat) (target : nat) : (nat * nat) = + let x = cash * target in + let y = Bitwise.shift_left tez 48n in + let x2 = x * x in + let y2 = y * y in + (tez * (3n * x2 + y2), cash * (3n * y2 + x2)) + (* ============================================================================= * Entrypoint Functions * ============================================================================= *) @@ -454,7 +461,7 @@ let update_consumer (operations, storage : result) : result = // TODO : when ligo is fixed let consumer = match (Tezos.get_entrypoint_opt "cfmm_price" storage.consumerEntrypoint : ((nat * nat) contract) option) with | None -> (failwith error_CANNOT_GET_CFMM_PRICE_ENTRYPOINT_FROM_CONSUMER : (nat * nat) contract) | Some c -> c in - ((Tezos.transaction (storage.tezPool, storage.cashPool) 0mutez consumer) :: operations, + ((Tezos.transaction (marginal_price storage.tezPool storage.cashPool storage.target) 0mutez consumer) :: operations, {storage with lastOracleUpdate = Tezos.now}) (* ============================================================================= diff --git a/ctez.mligo b/ctez.mligo index 6b2c9c85..78ccd173 100644 --- a/ctez.mligo +++ b/ctez.mligo @@ -86,14 +86,6 @@ let get_ctez_mint_or_burn (fa12_address : address) : (int * address) contract = | None -> (failwith error_CTEZ_FA12_CONTRACT_MISSING_MINT_OR_BURN_ENTRYPOINT : (int * address) contract) | Some c -> c -let get_price (target : nat) (cash : nat) (token : nat) : nat = - let (x,y) = (cash, token) in - let a = target in - let ax2 = x * x * a * a in - let by2 = Bitwise.shift_left (y * y) 96n in - let num = y * (3n * ax2 + by2) in - let denom = x * (ax2 + 3n * by2) in - num/denom (* Entrypoint Functions *) @@ -182,7 +174,7 @@ let mint_or_burn (s : storage) (p : mint_or_burn) : result = let get_target (storage : storage) (callback : nat contract) : result = ([Tezos.transaction storage.target 0mutez callback], storage) -let cfmm_price (storage : storage) (tez : nat) (token : nat) : result = +let cfmm_price (storage : storage) (price_numerator : nat) (price_denominator : nat) : result = if Tezos.sender <> storage.cfmm_address then (failwith error_CALLER_MUST_BE_CFMM : result) else @@ -200,7 +192,7 @@ let cfmm_price (storage : storage) (tez : nat) (token : nat) : result = for each day over or under the target by more than 1/64th. *) - let price : nat = get_price target tez token in + let price : nat = (Bitwise.shift_left price_numerator 48n) / price_denominator in let target_less_price : int = target - price in let d_drift = let x = Bitwise.shift_left (abs (target_less_price * target_less_price)) 10n in @@ -213,13 +205,13 @@ let cfmm_price (storage : storage) (tez : nat) (token : nat) : result = else storage.drift - d_drift in - let cfmm_address = storage.cfmm_address in - let txndata_ctez_target = target in - let entrypoint_ctez_target = - (match (Tezos.get_contract_opt cfmm_address : nat contract option) with -// TODO : when ligo is fixed: (match (Tezos.get_entrypoint_opt "ctezTarget" cfmm_address : nat contract option) with + let cfmm_address = storage.cfmm_address in + let txndata_ctez_target = target in + let entrypoint_ctez_target = + (match (Tezos.get_contract_opt cfmm_address : nat contract option) with +// TODO : when ligo is fixed: (match (Tezos.get_entrypoint_opt "ctezTarget" cfmm_address : nat contract option) with | None -> (failwith error_INVALID_CTEZ_TARGET_ENTRYPOINT : nat contract) - | Some c -> c ) in + | Some c -> c ) in let op_ctez_target = Tezos.transaction txndata_ctez_target 0tez entrypoint_ctez_target in ([op_ctez_target], {storage with drift = drift ; last_drift_update = Tezos.now ; target = target}) @@ -234,4 +226,3 @@ let main (p, s : parameter * storage) : result = | Cfmm_price (x,y) -> (cfmm_price s x y : result) | Set_addresses xs -> (set_addresses s xs : result) | Get_target t -> (get_target s t : result) - From 28d7c7d3c8560556e3614b344223436d01fc74aa Mon Sep 17 00:00:00 2001 From: shekhar-shubhendu Date: Mon, 13 Sep 2021 16:24:24 +0200 Subject: [PATCH 63/92] feat: update frontend to match new cfmm --- frontend/app/.env | 10 +- frontend/app/package.json | 4 +- frontend/app/src/api/contracts.ts | 2 +- frontend/app/src/contracts/cfmm.ts | 13 +- frontend/app/src/interfaces/cfmm.ts | 6 +- .../app/src/pages/BuySell/AddLiquidity.tsx | 4 +- frontend/app/src/pages/BuySell/Conversion.tsx | 11 +- .../app/src/pages/BuySell/RemoveLiquidity.tsx | 6 +- frontend/app/yarn.lock | 121 ++++++++++-------- 9 files changed, 93 insertions(+), 84 deletions(-) diff --git a/frontend/app/.env b/frontend/app/.env index e1c87ff7..2865dca2 100644 --- a/frontend/app/.env +++ b/frontend/app/.env @@ -1,11 +1,11 @@ REACT_APP_APP_NAME=CTez -REACT_APP_CTEZ_CONTRACT=KT1CQPxuLvFQHaoY9J41UiHxZoC1ME3tv7Lv -REACT_APP_CFMM_CONTRACT=KT1A3qFLhy1Crg71tfbJxcFNcnY29N5PeTyn -REACT_APP_CTEZ_FA12_CONTRACT=KT1NhGJuuvMbdmtV5cyJrxSx3EFavayHVZbD -REACT_APP_LQT_FA12_CONTRACT=KT1WVDvjmTLu3WXykLrrVU3nkqssrPf72QTF +REACT_APP_CTEZ_CONTRACT=KT1Njsf4ZGWf4guiRqnf5DcwcQYX4FFXHEWf +REACT_APP_CFMM_CONTRACT=KT1QkMukrumdAV95z5cNDebCimGKm3gXPUY1 +REACT_APP_CTEZ_FA12_CONTRACT=KT1CTTn4SL6HjdMAcr9rn2MrMbxFw56n9XH1 +REACT_APP_LQT_FA12_CONTRACT=KT1LbuKnHgtbyryDT8WtDQbHKZiBe6aP2S4r REACT_APP_NETWORK_TYPE=granadanet REACT_APP_RPC_URL=https://granadanet.smartpy.io REACT_APP_RPC_PORT=443 REACT_APP_TZKT=https://api.granadanet.tzkt.io REACT_APP_TZKT_PORT=443 -REACT_APP_CONTRACT_DEPLOYMENT_DATE=2021-09-02 \ No newline at end of file +REACT_APP_CONTRACT_DEPLOYMENT_DATE=2021-09-13 \ No newline at end of file diff --git a/frontend/app/package.json b/frontend/app/package.json index 9119cb3e..b869accc 100644 --- a/frontend/app/package.json +++ b/frontend/app/package.json @@ -11,8 +11,8 @@ "@material-ui/icons": "^5.0.0-alpha.26", "@material-ui/pickers": "4.0.0-alpha.12", "@reduxjs/toolkit": "^1.5.1", - "@taquito/beacon-wallet": "^9.0.1", - "@taquito/taquito": "^9.0.1", + "@taquito/beacon-wallet": "^10.1.1", + "@taquito/taquito": "^10.1.1", "axios": "^0.21.1", "bignumber.js": "^9.0.1", "blockies-ts": "^1.0.0", diff --git a/frontend/app/src/api/contracts.ts b/frontend/app/src/api/contracts.ts index 85c7d82a..4b4cd1e0 100644 --- a/frontend/app/src/api/contracts.ts +++ b/frontend/app/src/api/contracts.ts @@ -24,7 +24,7 @@ export const getBaseStats = async (userAddress?: string): Promise => const cTez7dayStorage = await getPrevCTezStorage(prevStorageDays, userAddress); const prevTarget = Number(cTez7dayStorage.target) / 2 ** 48; const currentTarget = cTezStorage.target.toNumber() / 2 ** 48; - const currentPrice = cfmmStorage.cashPool.toNumber() / cfmmStorage.tokenPool.toNumber(); + const currentPrice = cfmmStorage.tezPool.toNumber() / cfmmStorage.cashPool.toNumber(); const premium = currentPrice === currentTarget ? 0 : currentPrice / currentTarget - 1.0; const drift = cTezStorage.drift.toNumber(); const currentAnnualDrift = (1.0 + drift / 2 ** 48) ** (365.25 * 24 * 3600) - 1.0; diff --git a/frontend/app/src/contracts/cfmm.ts b/frontend/app/src/contracts/cfmm.ts index 7ae750e0..dfb19151 100644 --- a/frontend/app/src/contracts/cfmm.ts +++ b/frontend/app/src/contracts/cfmm.ts @@ -128,11 +128,11 @@ export const removeLiquidity = async ( return hash.opHash; }; -export const cashToToken = async (args: CashToTokenParams): Promise => { +export const tezToCash = async (args: CashToTokenParams): Promise => { const hash = await executeMethod( cfmm, - 'cashToToken', - [args.to, args.minTokensBought * 1e6, args.deadline.toISOString()], + 'tezToCash', + [args.to, args.minTokensBought * 1e6, args.deadline.toISOString(), 4], undefined, args.amount * 1e6, true, @@ -140,10 +140,7 @@ export const cashToToken = async (args: CashToTokenParams): Promise => { return hash; }; -export const tokenToCash = async ( - args: TokenToCashParams, - userAddress: string, -): Promise => { +export const cashToTez = async (args: TokenToCashParams, userAddress: string): Promise => { const tezos = getTezosInstance(); const CTezFa12 = await getCTezFa12Contract(); const batchOps: WalletParamsWithKind[] = await getTokenAllowanceOps( @@ -157,7 +154,7 @@ export const tokenToCash = async ( { kind: OpKind.TRANSACTION, ...cfmm.methods - .tokenToCash( + .cashToTez( args.to, args.tokensSold * 1e6, args.minCashBought * 1e6, diff --git a/frontend/app/src/interfaces/cfmm.ts b/frontend/app/src/interfaces/cfmm.ts index 149b5573..4c7311a4 100644 --- a/frontend/app/src/interfaces/cfmm.ts +++ b/frontend/app/src/interfaces/cfmm.ts @@ -40,10 +40,10 @@ export interface TokenToTokenParams { } export interface CfmmStorage { - tokenPool: BigNumber; + tezPool: BigNumber; cashPool: BigNumber; - pendingPoolUpdates: BigNumber; - tokenAddress: string; + target: BigNumber; + cashAddress: string; lqtAddress: string; lastOracleUpdate: Date; consumerEntrypoint: string; diff --git a/frontend/app/src/pages/BuySell/AddLiquidity.tsx b/frontend/app/src/pages/BuySell/AddLiquidity.tsx index 420a0572..d60744d4 100644 --- a/frontend/app/src/pages/BuySell/AddLiquidity.tsx +++ b/frontend/app/src/pages/BuySell/AddLiquidity.tsx @@ -48,10 +48,10 @@ const AddLiquidityComponent: React.FC = ({ t }) => { const calcMaxToken = (slippage: number, cashDeposited: number) => { if (cfmmStorage) { - const { tokenPool, cashPool } = cfmmStorage; + const { tezPool, cashPool } = cfmmStorage; const cash = cashDeposited * 1e6; const max = - Math.ceil(((cash * tokenPool.toNumber()) / cashPool.toNumber()) * (1 + slippage * 0.01)) / + Math.ceil(((cash * cashPool.toNumber()) / tezPool.toNumber()) * (1 + slippage * 0.01)) / 1e6; setMaxToken(Number(max.toFixed(6))); } else { diff --git a/frontend/app/src/pages/BuySell/Conversion.tsx b/frontend/app/src/pages/BuySell/Conversion.tsx index 32609d72..8898f570 100644 --- a/frontend/app/src/pages/BuySell/Conversion.tsx +++ b/frontend/app/src/pages/BuySell/Conversion.tsx @@ -21,7 +21,7 @@ import { useHistory } from 'react-router-dom'; import Page from '../../components/Page'; import FormikTextField from '../../components/TextField'; import { useWallet } from '../../wallet/hooks'; -import { cashToToken, cfmmError, tokenToCash } from '../../contracts/cfmm'; +import { tezToCash, cfmmError, cashToTez } from '../../contracts/cfmm'; import { TezosIcon } from '../../components/TezosIcon'; import { CTezIcon } from '../../components/CTezIcon/CTezIcon'; import { logger } from '../../utils/logger'; @@ -53,10 +53,9 @@ const ConvertComponent: React.FC = ({ t, formType }) => { const calcMinBuyValue = (slippage: number, amount: number) => { if (cfmmStorage) { - const { tokenPool, cashPool } = cfmmStorage; + const { tezPool, cashPool } = cfmmStorage; const cashSold = amount * 1e6; - const [aPool, bPool] = - formType === 'tezToCtez' ? [tokenPool, cashPool] : [cashPool, tokenPool]; + const [aPool, bPool] = formType === 'tezToCtez' ? [cashPool, tezPool] : [tezPool, cashPool]; const tokWithoutSlippage = (cashSold * 997 * aPool.toNumber()) / (bPool.toNumber() * 1000 + cashSold * 997) / 1e6; const tok = tokWithoutSlippage * (1 - slippage * 0.01); @@ -93,13 +92,13 @@ const ConvertComponent: React.FC = ({ t, formType }) => { const deadline = addMinutes(new Date(), formData.deadline); const result = formType === 'tezToCtez' - ? await cashToToken({ + ? await tezToCash({ amount: formData.amount, deadline, minTokensBought: minBuyValue, to: formData.to, }) - : await tokenToCash( + : await cashToTez( { deadline, minCashBought: minBuyValue, diff --git a/frontend/app/src/pages/BuySell/RemoveLiquidity.tsx b/frontend/app/src/pages/BuySell/RemoveLiquidity.tsx index 67c8a308..4e7af148 100644 --- a/frontend/app/src/pages/BuySell/RemoveLiquidity.tsx +++ b/frontend/app/src/pages/BuySell/RemoveLiquidity.tsx @@ -49,11 +49,11 @@ const RemoveLiquidityComponent: React.FC = ({ t }) => { const calcMinValues = (slippage: number, lqtBurned: number) => { if (cfmmStorage) { - const { cashPool, tokenPool, lqtTotal } = cfmmStorage; + const { cashPool, tezPool, lqtTotal } = cfmmStorage; const cashWithdraw = - ((lqtBurned * cashPool.toNumber()) / lqtTotal.toNumber()) * (1 - slippage * 0.01); + ((lqtBurned * tezPool.toNumber()) / lqtTotal.toNumber()) * (1 - slippage * 0.01); const tokenWithdraw = - ((lqtBurned * tokenPool.toNumber()) / lqtTotal.toNumber()) * (1 - slippage * 0.01); + ((lqtBurned * cashPool.toNumber()) / lqtTotal.toNumber()) * (1 - slippage * 0.01); setValues({ cashWithdraw: Number((cashWithdraw / 1e6).toFixed(6)), tokenWithdraw: Number((tokenWithdraw / 1e6).toFixed(6)), diff --git a/frontend/app/yarn.lock b/frontend/app/yarn.lock index 34431f81..346508f7 100644 --- a/frontend/app/yarn.lock +++ b/frontend/app/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -"@airgap/beacon-sdk@^2.2.5", "@airgap/beacon-sdk@^2.2.7": +"@airgap/beacon-sdk@^2.2.7": version "2.2.7" resolved "https://registry.yarnpkg.com/@airgap/beacon-sdk/-/beacon-sdk-2.2.7.tgz#8dcf041f9c98f2648d0d0a76698027717873ece3" integrity sha512-07tEUuRWjjX+eRWyX4r9b5TSwUvJZntiuUHLuaVFkZMa1E1BUuLKFB7z5KN9wBSzmUP6viASi3F2/5OFnomNkQ== @@ -15,6 +15,19 @@ libsodium-wrappers "0.7.8" qrcode-generator "1.4.4" +"@airgap/beacon-sdk@^2.3.0": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@airgap/beacon-sdk/-/beacon-sdk-2.3.2.tgz#310c76186a1cfc8afd2bab70f1b5a57da76dcb6b" + integrity sha512-PAZ3n/RJaaBqgwQrFo1xrpAcMY/x9W3eZKZyKZMZuKVTB60spiy3KvSj7OYsk+03/nvCB+qDpllkUGFZ5GxMJg== + dependencies: + "@types/chrome" "0.0.115" + "@types/libsodium-wrappers" "0.7.7" + axios "0.21.1" + bignumber.js "9.0.0" + bs58check "2.1.2" + libsodium-wrappers "0.7.8" + qrcode-generator "1.4.4" + "@babel/code-frame@7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" @@ -2131,68 +2144,68 @@ "@svgr/plugin-svgo" "^5.5.0" loader-utils "^2.0.0" -"@taquito/beacon-wallet@^9.0.1": - version "9.0.1" - resolved "https://registry.yarnpkg.com/@taquito/beacon-wallet/-/beacon-wallet-9.0.1.tgz#ae97959bbdc17ffd6addf7636b09805718feeb9d" - integrity sha512-Nkkcfmotd1laTYP4uunI15OiCElBLv2fG8fDGY0Vb+0/2sokBPu60LEaQ1++2FBycRxWQ9Yph3ODxjNeIZMKMw== +"@taquito/beacon-wallet@^10.1.1": + version "10.1.1" + resolved "https://registry.yarnpkg.com/@taquito/beacon-wallet/-/beacon-wallet-10.1.1.tgz#72f73b6a33870f102989d500df9d8802501290bf" + integrity sha512-YtYmPKVO1GkA8/YbUq8h/utLlsDvNEA59XC+MUprh0QLyBCLtsh5RJmG5xxOFTRijmk1SMvDC18B1O9EBWALrw== dependencies: - "@airgap/beacon-sdk" "^2.2.5" - "@taquito/taquito" "^9.0.1" - "@taquito/utils" "^9.0.1" + "@airgap/beacon-sdk" "^2.3.0" + "@taquito/taquito" "^10.1.1" + "@taquito/utils" "^10.1.1" -"@taquito/http-utils@^9.0.1": - version "9.0.1" - resolved "https://registry.yarnpkg.com/@taquito/http-utils/-/http-utils-9.0.1.tgz#be8329520266d75e40665e2a38a4e6d8c01899a6" - integrity sha512-xSBToyKekno6Q0tPefLfYrhYelb6rigQavXQglCiZZt5N2u591sC8IEdYlq3iFe6teVg03phtVY8dGWGFkHOYw== +"@taquito/http-utils@^10.1.1": + version "10.1.1" + resolved "https://registry.yarnpkg.com/@taquito/http-utils/-/http-utils-10.1.1.tgz#207f1a57fc3bc5c55da3024dd460af198cba673d" + integrity sha512-nlaYjcfyAa89GT8YWYpbGrrR4hbQmUsrhojSX3emgyWTbN4QkZwke2h0eGJEOFQoUHeGSF1U8bVGOk6+jnlpIQ== dependencies: xhr2-cookies "^1.1.0" -"@taquito/michel-codec@^9.0.1": - version "9.0.1" - resolved "https://registry.yarnpkg.com/@taquito/michel-codec/-/michel-codec-9.0.1.tgz#bd066b6218dcc155ffc641877806465cf2f77a14" - integrity sha512-zUdE6P76p89dUZLjUz3aBaKy7qgGJv8Vkt7QaIcP0FmO78RIV796gaXVXfm9UByXeHnUG1ca3JYX2SKD/iYWpA== +"@taquito/michel-codec@^10.1.1": + version "10.1.1" + resolved "https://registry.yarnpkg.com/@taquito/michel-codec/-/michel-codec-10.1.1.tgz#eaba7d08fe2bc87778ed7ff9fa52c9a787b1b09e" + integrity sha512-/5gZetXAQ71b6Xrj1/gfgHopcR56xcVKCinEWIY8Bs2NK6RprNiXoh/cQVTybAyGwzesNCK8/7N7Fe6Os12yqA== -"@taquito/michelson-encoder@^9.0.1": - version "9.0.1" - resolved "https://registry.yarnpkg.com/@taquito/michelson-encoder/-/michelson-encoder-9.0.1.tgz#39fd2d52fa98b0421259af9bb90dc9ea2641786b" - integrity sha512-AoSEcKHpyKsIOcqubCxsA7gWf3+UO1wR67HMBZ9cQdNQKaRtdOm508dvL0DgWPQaNf6x1cw1s1eQn+p+N6nInQ== +"@taquito/michelson-encoder@^10.1.1": + version "10.1.1" + resolved "https://registry.yarnpkg.com/@taquito/michelson-encoder/-/michelson-encoder-10.1.1.tgz#0b27c4d4193627d09a8b7ead582cfddfdce69e7c" + integrity sha512-Q1qGfOu0Gbdfj9AbBVCc5sxBzEZkGxFSAPCK5nzCG3z1Jx3nGXDaYyonQa75lNWxYze9N+c3ETF4rd8OKsmihQ== dependencies: - "@taquito/rpc" "^9.0.1" - "@taquito/utils" "^9.0.1" + "@taquito/rpc" "^10.1.1" + "@taquito/utils" "^10.1.1" bignumber.js "^9.0.1" fast-json-stable-stringify "^2.1.0" -"@taquito/rpc@^9.0.1": - version "9.0.1" - resolved "https://registry.yarnpkg.com/@taquito/rpc/-/rpc-9.0.1.tgz#9ec943faf90565d0331552dea56d16c7bf20e8de" - integrity sha512-e78jsKxlxroC2wyfDMVIH+LCHQTMH6Liu0fTaZYiav3wIqZNK6LFgLSbLBoiT2iY0daoPFFHHDCAsbhoYU/WJQ== +"@taquito/rpc@^10.1.1": + version "10.1.1" + resolved "https://registry.yarnpkg.com/@taquito/rpc/-/rpc-10.1.1.tgz#889cce8551282fc1a05252cda8276bf3a709541d" + integrity sha512-AMYjBBWz3t0OXoVg8zge1OmcNUjpHFatSiV1RBr1g0fLNeFSlIqvsP4MwmR817ot95fxhOoIjgETPlDOOqwRDQ== dependencies: - "@taquito/http-utils" "^9.0.1" + "@taquito/http-utils" "^10.1.1" bignumber.js "^9.0.1" - lodash "^4.17.20" + lodash "^4.17.21" -"@taquito/taquito@^9.0.1": - version "9.0.1" - resolved "https://registry.yarnpkg.com/@taquito/taquito/-/taquito-9.0.1.tgz#1195411e9cc8c88f7c9c59ec633ba2f1aab93919" - integrity sha512-+7UkZndDKVoPla+0WFPTxK5EfTjwbWyzthinzOA3OrfPLJIeYUMd5LJcR02r9lL4YXqLwIBgrrPoSu/CqiUxPQ== - dependencies: - "@taquito/http-utils" "^9.0.1" - "@taquito/michel-codec" "^9.0.1" - "@taquito/michelson-encoder" "^9.0.1" - "@taquito/rpc" "^9.0.1" - "@taquito/utils" "^9.0.1" +"@taquito/taquito@^10.1.1": + version "10.1.1" + resolved "https://registry.yarnpkg.com/@taquito/taquito/-/taquito-10.1.1.tgz#b5e79544b66d32199f4545acd3132b640915e2bf" + integrity sha512-95MJgfErpKCRDcUpt9C5xDeCA7cwugWIETSPCP8h22NyN5bovRIb92gvk1uun0a/pm9sBPfTSiG/2Q7rXsc7yQ== + dependencies: + "@taquito/http-utils" "^10.1.1" + "@taquito/michel-codec" "^10.1.1" + "@taquito/michelson-encoder" "^10.1.1" + "@taquito/rpc" "^10.1.1" + "@taquito/utils" "^10.1.1" bignumber.js "^9.0.1" - rx-sandbox "^1.0.3" + rx-sandbox "^1.0.4" rxjs "^6.6.3" -"@taquito/utils@^9.0.1": - version "9.0.1" - resolved "https://registry.yarnpkg.com/@taquito/utils/-/utils-9.0.1.tgz#add8b35071895ed316abe20b1d308df46062cf9f" - integrity sha512-b9vLbcjNtOm3popJPbFjWdU69ydz15QwHuRyiNPI5eEd47624+2hv1wMg1yyOdxyJhfKLhkfO/46B+lETjLopg== +"@taquito/utils@^10.1.1": + version "10.1.1" + resolved "https://registry.yarnpkg.com/@taquito/utils/-/utils-10.1.1.tgz#d17eddd9e65e42a1f01787417ea0a86a8797d3f3" + integrity sha512-fqY5uPAc5Yuns0YwgzSToKuvSN0ba/ZOWpaNgCSaFeOyWMW2ZAIDpfDcr70+MGqe81EwKM1FO6r61hlb06ElFw== dependencies: blakejs "^1.1.0" bs58check "^2.1.2" - buffer "^5.6.0" + buffer "^6.0.3" "@testing-library/dom@^7.28.1": version "7.29.6" @@ -3913,13 +3926,13 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.6.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== dependencies: base64-js "^1.3.1" - ieee754 "^1.1.13" + ieee754 "^1.2.1" builtin-modules@^3.1.0: version "3.2.0" @@ -4066,9 +4079,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001181: - version "1.0.30001191" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001191.tgz#bacb432b6701f690c8c5f7c680166b9a9f0843d9" - integrity sha512-xJJqzyd+7GCJXkcoBiQ1GuxEiOBCLQ0aVW9HMekifZsAVGdj5eJ4mFB9fEhSHipq9IOk/QXFJUiIr9lZT+EsGw== + version "1.0.30001257" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001257.tgz" + integrity sha512-JN49KplOgHSXpIsVSF+LUyhD8PUp6xPpAXeRrrcBh4KBeP7W864jHn6RvzJgDlrReyeVjMFJL3PLpPvKIxlIHA== capture-exit@^2.0.0: version "2.0.0" @@ -6927,7 +6940,7 @@ identity-obj-proxy@3.0.0: dependencies: harmony-reflect "^1.4.6" -ieee754@^1.1.13, ieee754@^1.1.4: +ieee754@^1.1.4, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -11230,7 +11243,7 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rx-sandbox@^1.0.3: +rx-sandbox@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/rx-sandbox/-/rx-sandbox-1.0.4.tgz#821a1d64e5f0d88658da7a5dbbd735b13277648b" integrity sha512-+/9MHDYNoF9ca/2RR+L2LloXXeQyIR3k/wjK03IicrxxlbkhmKF4ejPiWeafMWDg7otF+pnX5NE/8v/rX6ICJA== From c8c1c39cdec18fc3536562feeab376641f02a41e Mon Sep 17 00:00:00 2001 From: shekhar-shubhendu Date: Mon, 13 Sep 2021 16:24:43 +0200 Subject: [PATCH 64/92] feat: update cfmm storage for deployment --- cfmm_initial_storage.mligo | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cfmm_initial_storage.mligo b/cfmm_initial_storage.mligo index b55b662a..87ae6427 100644 --- a/cfmm_initial_storage.mligo +++ b/cfmm_initial_storage.mligo @@ -1,8 +1,9 @@ -{ tokenPool = 1n ; +{ tezPool = 1n ; cashPool = 1n ; + target = 1n; lqtTotal = 1n ; - pendingPoolUpdates = 0n ; - tokenAddress = ("FA12_CTEZ" : address) ; + ctez_address = ("CTEZ_ADDRESS" : address) ; + cashAddress = ("FA12_CTEZ" : address) ; lqtAddress = ("tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" : address) ; lastOracleUpdate = ("2021-01-01T00:00:00Z" : timestamp) ; consumerEntrypoint = ("CTEZ_ADDRESS%cfmm_price" : address) ; From c4c3f1574ab044c8ea21a8fc6c6614ba6f248eaa Mon Sep 17 00:00:00 2001 From: shekhar-shubhendu Date: Tue, 14 Sep 2021 15:43:24 +0200 Subject: [PATCH 65/92] feat: update add liquidity and conversion --- ctez.mligo | 3 +- frontend/app/.env | 10 +- frontend/app/.eslintrc.js | 1 + frontend/app/src/contracts/cfmm.ts | 2 + .../app/src/pages/BuySell/AddLiquidity.tsx | 4 +- frontend/app/src/pages/BuySell/Conversion.tsx | 96 +++++++++++++++++-- 6 files changed, 100 insertions(+), 16 deletions(-) diff --git a/ctez.mligo b/ctez.mligo index 78ccd173..604c93ac 100644 --- a/ctez.mligo +++ b/ctez.mligo @@ -208,8 +208,7 @@ let cfmm_price (storage : storage) (price_numerator : nat) (price_denominator : let cfmm_address = storage.cfmm_address in let txndata_ctez_target = target in let entrypoint_ctez_target = - (match (Tezos.get_contract_opt cfmm_address : nat contract option) with -// TODO : when ligo is fixed: (match (Tezos.get_entrypoint_opt "ctezTarget" cfmm_address : nat contract option) with + (match (Tezos.get_entrypoint_opt "%ctezTarget" cfmm_address : nat contract option) with | None -> (failwith error_INVALID_CTEZ_TARGET_ENTRYPOINT : nat contract) | Some c -> c ) in let op_ctez_target = Tezos.transaction txndata_ctez_target 0tez entrypoint_ctez_target in diff --git a/frontend/app/.env b/frontend/app/.env index 2865dca2..9cf06411 100644 --- a/frontend/app/.env +++ b/frontend/app/.env @@ -1,11 +1,11 @@ REACT_APP_APP_NAME=CTez -REACT_APP_CTEZ_CONTRACT=KT1Njsf4ZGWf4guiRqnf5DcwcQYX4FFXHEWf -REACT_APP_CFMM_CONTRACT=KT1QkMukrumdAV95z5cNDebCimGKm3gXPUY1 -REACT_APP_CTEZ_FA12_CONTRACT=KT1CTTn4SL6HjdMAcr9rn2MrMbxFw56n9XH1 -REACT_APP_LQT_FA12_CONTRACT=KT1LbuKnHgtbyryDT8WtDQbHKZiBe6aP2S4r +REACT_APP_CTEZ_CONTRACT=KT1KmgDGPwkMszJa72bmaVuKCfqw4kPTowWy +REACT_APP_CFMM_CONTRACT=KT1Wmj5xaufYqY2x9HMotmydMgDw5jfG4fuq +REACT_APP_CTEZ_FA12_CONTRACT=KT1QBSXZeKrjF5LJTn2PGyySYNLCWPbN4ePA +REACT_APP_LQT_FA12_CONTRACT=KT1MRDopKKEFRb9DCaRb59ePCqfoELjtVXu6 REACT_APP_NETWORK_TYPE=granadanet REACT_APP_RPC_URL=https://granadanet.smartpy.io REACT_APP_RPC_PORT=443 REACT_APP_TZKT=https://api.granadanet.tzkt.io REACT_APP_TZKT_PORT=443 -REACT_APP_CONTRACT_DEPLOYMENT_DATE=2021-09-13 \ No newline at end of file +REACT_APP_CONTRACT_DEPLOYMENT_DATE=2021-09-14 \ No newline at end of file diff --git a/frontend/app/.eslintrc.js b/frontend/app/.eslintrc.js index 16246898..73a64c4f 100644 --- a/frontend/app/.eslintrc.js +++ b/frontend/app/.eslintrc.js @@ -61,5 +61,6 @@ module.exports = { 'no-nested-ternary': 'warn', 'no-unneeded-ternary': 'warn', '@typescript-eslint/naming-convention': 'warn', + 'no-bitwise': 'off', }, }; diff --git a/frontend/app/src/contracts/cfmm.ts b/frontend/app/src/contracts/cfmm.ts index dfb19151..0fc19508 100644 --- a/frontend/app/src/contracts/cfmm.ts +++ b/frontend/app/src/contracts/cfmm.ts @@ -129,6 +129,7 @@ export const removeLiquidity = async ( }; export const tezToCash = async (args: CashToTokenParams): Promise => { + console.log('>>', args.minTokensBought * 1e6, args.amount * 1e6); const hash = await executeMethod( cfmm, 'tezToCash', @@ -159,6 +160,7 @@ export const cashToTez = async (args: TokenToCashParams, userAddress: string): P args.tokensSold * 1e6, args.minCashBought * 1e6, args.deadline.toISOString(), + 4, ) .toTransferParams(), }, diff --git a/frontend/app/src/pages/BuySell/AddLiquidity.tsx b/frontend/app/src/pages/BuySell/AddLiquidity.tsx index d60744d4..62ff0669 100644 --- a/frontend/app/src/pages/BuySell/AddLiquidity.tsx +++ b/frontend/app/src/pages/BuySell/AddLiquidity.tsx @@ -61,10 +61,10 @@ const AddLiquidityComponent: React.FC = ({ t }) => { const calcMinPoolPercent = (slippage: number, cashDeposited: number) => { if (cfmmStorage) { - const { cashPool, lqtTotal } = cfmmStorage; + const { tezPool, lqtTotal } = cfmmStorage; const cash = cashDeposited * 1e6; const minLQTMinted = - ((cash * lqtTotal.toNumber()) / cashPool.toNumber()) * (1 - slippage * 0.01); + ((cash * lqtTotal.toNumber()) / tezPool.toNumber()) * (1 - slippage * 0.01); const minPool = (minLQTMinted / (lqtTotal.toNumber() + minLQTMinted)) * 100; setMinLQT(Number(Math.floor(minLQTMinted).toFixed())); setMinPoolPercent(Number(minPool.toFixed(6))); diff --git a/frontend/app/src/pages/BuySell/Conversion.tsx b/frontend/app/src/pages/BuySell/Conversion.tsx index 8898f570..3d73d566 100644 --- a/frontend/app/src/pages/BuySell/Conversion.tsx +++ b/frontend/app/src/pages/BuySell/Conversion.tsx @@ -43,6 +43,70 @@ interface ConversionFormParams { amount: number; } +const FEE = 9995; +const FEE_DENOM = 10000; + +const newtonDxToDyRecursive = ( + xp: number, + xp2: number, + x3yPlusY3x: number, + y: number, + dyApprox: number, + rounds: number, +): number => { + if (rounds <= 0) return dyApprox; + const yp = y - dyApprox; + const yp2 = Math.abs(yp * yp); + const num = Math.abs(xp * yp * (xp2 + yp2) - x3yPlusY3x); + const denom = xp * (xp2 + 3 * yp2); + const adjust = num / denom; + const newDyApprox = dyApprox + adjust; + return newtonDxToDyRecursive(xp, xp2, x3yPlusY3x, y, newDyApprox, rounds - 1); +}; + +const newtonDxToDy = (x: number, y: number, dx: number, rounds: number): number => { + const xp = x + dx; + const xp2 = xp * xp; + const x3yPlusY3x = x * y * (x * x + y * y); + return newtonDxToDyRecursive(xp, xp2, x3yPlusY3x, y, 0, rounds); +}; + +const tradeDTezForDCash = ( + tez: number, + cash: number, + dtez: number, + target: number, + rounds = 4, +): number => { + const x = tez * 2 ** 48; + const y = target * cash; + const dx = dtez * 2 ** 48; + const dyApprox = newtonDxToDy(x, y, dx, rounds); + const dCashApprox = dyApprox / target; + if (tez - dCashApprox <= 0) { + throw new Error('CASH POOL MINUS CASH WITHDRAWN IS NEGATIVE'); + } + return dCashApprox; +}; + +const tradeDCashForDTez = ( + tez: number, + cash: number, + dcash: number, + target: number, + rounds = 4, +): number => { + const y = tez * 2 ** 48; + const x = target * cash; + const dx = target * dcash; + const dyApprox = newtonDxToDy(x, y, dx, rounds); + const dtezApprox = dyApprox / 2 ** 48; + if (tez - dtezApprox <= 0) { + throw new Error('TEZ POOL MINUS TEZ WITHDRAWN IS NEGATIVE'); + } + return dtezApprox; +}; + const ConvertComponent: React.FC = ({ t, formType }) => { const [{ pkh: userAddress }] = useWallet(); const { addToast } = useToasts(); @@ -53,14 +117,32 @@ const ConvertComponent: React.FC = ({ t, formType }) => { const calcMinBuyValue = (slippage: number, amount: number) => { if (cfmmStorage) { - const { tezPool, cashPool } = cfmmStorage; + const { tezPool, cashPool, target } = cfmmStorage; const cashSold = amount * 1e6; - const [aPool, bPool] = formType === 'tezToCtez' ? [cashPool, tezPool] : [tezPool, cashPool]; - const tokWithoutSlippage = - (cashSold * 997 * aPool.toNumber()) / (bPool.toNumber() * 1000 + cashSold * 997) / 1e6; - const tok = tokWithoutSlippage * (1 - slippage * 0.01); - setWithoutSlippage(Number(tokWithoutSlippage.toFixed(6))); - setMinBuyValue(Number(tok.toFixed(6))); + console.log(tezPool.toNumber(), cashPool.toNumber(), cashSold, 65536); + const bought = + formType === 'tezToCtez' + ? tradeDTezForDCash( + tezPool.toNumber(), + cashPool.toNumber(), + cashSold, + target.toNumber(), + 4, + ) + : tradeDCashForDTez( + tezPool.toNumber(), + cashPool.toNumber(), + cashSold, + target.toNumber(), + 4, + ); + + const boughtAfterFee = (bought * FEE) / FEE_DENOM; + console.log(bought, boughtAfterFee); + const tok = boughtAfterFee * (1 - slippage * 0.01); + console.log(boughtAfterFee); + setWithoutSlippage(Number((Math.floor(boughtAfterFee) / 1e6).toFixed(6))); + setMinBuyValue(Number((Math.floor(tok) / 1e6).toFixed(6))); } else { setMinBuyValue(-1); } From 549610a85467f92d7ea91a96e8f55f576909c437 Mon Sep 17 00:00:00 2001 From: shekhar-shubhendu Date: Tue, 14 Sep 2021 15:44:11 +0200 Subject: [PATCH 66/92] fix: remove console.log --- frontend/app/src/contracts/cfmm.ts | 1 - frontend/app/src/pages/BuySell/Conversion.tsx | 3 --- 2 files changed, 4 deletions(-) diff --git a/frontend/app/src/contracts/cfmm.ts b/frontend/app/src/contracts/cfmm.ts index 0fc19508..3d2e2abc 100644 --- a/frontend/app/src/contracts/cfmm.ts +++ b/frontend/app/src/contracts/cfmm.ts @@ -129,7 +129,6 @@ export const removeLiquidity = async ( }; export const tezToCash = async (args: CashToTokenParams): Promise => { - console.log('>>', args.minTokensBought * 1e6, args.amount * 1e6); const hash = await executeMethod( cfmm, 'tezToCash', diff --git a/frontend/app/src/pages/BuySell/Conversion.tsx b/frontend/app/src/pages/BuySell/Conversion.tsx index 3d73d566..13f05c82 100644 --- a/frontend/app/src/pages/BuySell/Conversion.tsx +++ b/frontend/app/src/pages/BuySell/Conversion.tsx @@ -119,7 +119,6 @@ const ConvertComponent: React.FC = ({ t, formType }) => { if (cfmmStorage) { const { tezPool, cashPool, target } = cfmmStorage; const cashSold = amount * 1e6; - console.log(tezPool.toNumber(), cashPool.toNumber(), cashSold, 65536); const bought = formType === 'tezToCtez' ? tradeDTezForDCash( @@ -138,9 +137,7 @@ const ConvertComponent: React.FC = ({ t, formType }) => { ); const boughtAfterFee = (bought * FEE) / FEE_DENOM; - console.log(bought, boughtAfterFee); const tok = boughtAfterFee * (1 - slippage * 0.01); - console.log(boughtAfterFee); setWithoutSlippage(Number((Math.floor(boughtAfterFee) / 1e6).toFixed(6))); setMinBuyValue(Number((Math.floor(tok) / 1e6).toFixed(6))); } else { From 4639f1c4686ebfe346f1f55bdf1c481831354123 Mon Sep 17 00:00:00 2001 From: shekhar-shubhendu Date: Tue, 14 Sep 2021 16:20:16 +0200 Subject: [PATCH 67/92] feat: fix price --- frontend/app/src/api/contracts.ts | 16 ++++- frontend/app/src/pages/BuySell/Conversion.tsx | 65 +------------------ frontend/app/src/utils/cfmmUtils.ts | 63 ++++++++++++++++++ 3 files changed, 79 insertions(+), 65 deletions(-) create mode 100644 frontend/app/src/utils/cfmmUtils.ts diff --git a/frontend/app/src/api/contracts.ts b/frontend/app/src/api/contracts.ts index 4b4cd1e0..64f5a59b 100644 --- a/frontend/app/src/api/contracts.ts +++ b/frontend/app/src/api/contracts.ts @@ -16,6 +16,16 @@ export const getPrevCTezStorage = async ( return storage; }; +const calculateMarginalPrice = (tez: number, cash: number, target: number): number => { + const x = cash * target; + const y = tez * 2 ** 48; + const x2 = x * x; + const y2 = y * y; + const nom = tez * (3 * x2 + y2); + const denom = cash * (3 * y2 + x2); + return (nom * 2 ** 48) / denom / 2 ** 48; +}; + export const getBaseStats = async (userAddress?: string): Promise => { const diffInDays = differenceInDays(new Date(), new Date(CONTRACT_DEPLOYMENT_DATE)); const prevStorageDays = diffInDays >= 7 ? 7 : diffInDays; @@ -24,7 +34,11 @@ export const getBaseStats = async (userAddress?: string): Promise => const cTez7dayStorage = await getPrevCTezStorage(prevStorageDays, userAddress); const prevTarget = Number(cTez7dayStorage.target) / 2 ** 48; const currentTarget = cTezStorage.target.toNumber() / 2 ** 48; - const currentPrice = cfmmStorage.tezPool.toNumber() / cfmmStorage.cashPool.toNumber(); + const currentPrice = calculateMarginalPrice( + cfmmStorage.tezPool.toNumber(), + cfmmStorage.cashPool.toNumber(), + cTezStorage.target.toNumber(), + ); const premium = currentPrice === currentTarget ? 0 : currentPrice / currentTarget - 1.0; const drift = cTezStorage.drift.toNumber(); const currentAnnualDrift = (1.0 + drift / 2 ** 48) ** (365.25 * 24 * 3600) - 1.0; diff --git a/frontend/app/src/pages/BuySell/Conversion.tsx b/frontend/app/src/pages/BuySell/Conversion.tsx index 13f05c82..07b40fa4 100644 --- a/frontend/app/src/pages/BuySell/Conversion.tsx +++ b/frontend/app/src/pages/BuySell/Conversion.tsx @@ -27,6 +27,7 @@ import { CTezIcon } from '../../components/CTezIcon/CTezIcon'; import { logger } from '../../utils/logger'; import { DEFAULT_SLIPPAGE } from '../../utils/globals'; import { useCfmmStorage } from '../../api/queries'; +import { tradeDTezForDCash, tradeDCashForDTez, FEE, FEE_DENOM } from '../../utils/cfmmUtils'; interface ConversionParams extends WithTranslation { formType: 'tezToCtez' | 'ctezToTez'; @@ -43,70 +44,6 @@ interface ConversionFormParams { amount: number; } -const FEE = 9995; -const FEE_DENOM = 10000; - -const newtonDxToDyRecursive = ( - xp: number, - xp2: number, - x3yPlusY3x: number, - y: number, - dyApprox: number, - rounds: number, -): number => { - if (rounds <= 0) return dyApprox; - const yp = y - dyApprox; - const yp2 = Math.abs(yp * yp); - const num = Math.abs(xp * yp * (xp2 + yp2) - x3yPlusY3x); - const denom = xp * (xp2 + 3 * yp2); - const adjust = num / denom; - const newDyApprox = dyApprox + adjust; - return newtonDxToDyRecursive(xp, xp2, x3yPlusY3x, y, newDyApprox, rounds - 1); -}; - -const newtonDxToDy = (x: number, y: number, dx: number, rounds: number): number => { - const xp = x + dx; - const xp2 = xp * xp; - const x3yPlusY3x = x * y * (x * x + y * y); - return newtonDxToDyRecursive(xp, xp2, x3yPlusY3x, y, 0, rounds); -}; - -const tradeDTezForDCash = ( - tez: number, - cash: number, - dtez: number, - target: number, - rounds = 4, -): number => { - const x = tez * 2 ** 48; - const y = target * cash; - const dx = dtez * 2 ** 48; - const dyApprox = newtonDxToDy(x, y, dx, rounds); - const dCashApprox = dyApprox / target; - if (tez - dCashApprox <= 0) { - throw new Error('CASH POOL MINUS CASH WITHDRAWN IS NEGATIVE'); - } - return dCashApprox; -}; - -const tradeDCashForDTez = ( - tez: number, - cash: number, - dcash: number, - target: number, - rounds = 4, -): number => { - const y = tez * 2 ** 48; - const x = target * cash; - const dx = target * dcash; - const dyApprox = newtonDxToDy(x, y, dx, rounds); - const dtezApprox = dyApprox / 2 ** 48; - if (tez - dtezApprox <= 0) { - throw new Error('TEZ POOL MINUS TEZ WITHDRAWN IS NEGATIVE'); - } - return dtezApprox; -}; - const ConvertComponent: React.FC = ({ t, formType }) => { const [{ pkh: userAddress }] = useWallet(); const { addToast } = useToasts(); diff --git a/frontend/app/src/utils/cfmmUtils.ts b/frontend/app/src/utils/cfmmUtils.ts new file mode 100644 index 00000000..4483b1e8 --- /dev/null +++ b/frontend/app/src/utils/cfmmUtils.ts @@ -0,0 +1,63 @@ +export const FEE = 9995; +export const FEE_DENOM = 10000; + +export const newtonDxToDyRecursive = ( + xp: number, + xp2: number, + x3yPlusY3x: number, + y: number, + dyApprox: number, + rounds: number, +): number => { + if (rounds <= 0) return dyApprox; + const yp = y - dyApprox; + const yp2 = Math.abs(yp * yp); + const num = Math.abs(xp * yp * (xp2 + yp2) - x3yPlusY3x); + const denom = xp * (xp2 + 3 * yp2); + const adjust = num / denom; + const newDyApprox = dyApprox + adjust; + return newtonDxToDyRecursive(xp, xp2, x3yPlusY3x, y, newDyApprox, rounds - 1); +}; + +export const newtonDxToDy = (x: number, y: number, dx: number, rounds: number): number => { + const xp = x + dx; + const xp2 = xp * xp; + const x3yPlusY3x = x * y * (x * x + y * y); + return newtonDxToDyRecursive(xp, xp2, x3yPlusY3x, y, 0, rounds); +}; + +export const tradeDTezForDCash = ( + tez: number, + cash: number, + dtez: number, + target: number, + rounds = 4, +): number => { + const x = tez * 2 ** 48; + const y = target * cash; + const dx = dtez * 2 ** 48; + const dyApprox = newtonDxToDy(x, y, dx, rounds); + const dCashApprox = dyApprox / target; + if (tez - dCashApprox <= 0) { + throw new Error('CASH POOL MINUS CASH WITHDRAWN IS NEGATIVE'); + } + return dCashApprox; +}; + +export const tradeDCashForDTez = ( + tez: number, + cash: number, + dcash: number, + target: number, + rounds = 4, +): number => { + const y = tez * 2 ** 48; + const x = target * cash; + const dx = target * dcash; + const dyApprox = newtonDxToDy(x, y, dx, rounds); + const dtezApprox = dyApprox / 2 ** 48; + if (tez - dtezApprox <= 0) { + throw new Error('TEZ POOL MINUS TEZ WITHDRAWN IS NEGATIVE'); + } + return dtezApprox; +}; From ab600d9bf0a136f25b1a69b99b958fb29d37bad0 Mon Sep 17 00:00:00 2001 From: shekhar-shubhendu Date: Tue, 14 Sep 2021 16:26:23 +0200 Subject: [PATCH 68/92] cleanup --- frontend/app/src/api/contracts.ts | 26 +------------------ .../src/components/OvenActions/MintOrBurn.tsx | 2 +- frontend/app/src/pages/MyOvenPage/index.tsx | 2 +- frontend/app/src/utils/cfmmUtils.ts | 25 ++++++++++++++++++ 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/frontend/app/src/api/contracts.ts b/frontend/app/src/api/contracts.ts index 64f5a59b..ef9b71ad 100644 --- a/frontend/app/src/api/contracts.ts +++ b/frontend/app/src/api/contracts.ts @@ -3,6 +3,7 @@ import { sub, format, differenceInDays } from 'date-fns'; import { getCfmmStorage, getLQTContractStorage } from '../contracts/cfmm'; import { getCtezStorage } from '../contracts/ctez'; import { BaseStats, CTezTzktStorage, UserLQTData } from '../interfaces'; +import { calculateMarginalPrice } from '../utils/cfmmUtils'; import { CONTRACT_DEPLOYMENT_DATE } from '../utils/globals'; import { getCTezTzktStorage, getLastBlockOfTheDay } from './tzkt'; @@ -16,16 +17,6 @@ export const getPrevCTezStorage = async ( return storage; }; -const calculateMarginalPrice = (tez: number, cash: number, target: number): number => { - const x = cash * target; - const y = tez * 2 ** 48; - const x2 = x * x; - const y2 = y * y; - const nom = tez * (3 * x2 + y2); - const denom = cash * (3 * y2 + x2); - return (nom * 2 ** 48) / denom / 2 ** 48; -}; - export const getBaseStats = async (userAddress?: string): Promise => { const diffInDays = differenceInDays(new Date(), new Date(CONTRACT_DEPLOYMENT_DATE)); const prevStorageDays = diffInDays >= 7 ? 7 : diffInDays; @@ -68,18 +59,3 @@ export const getUserLQTData = async (userAddress: string): Promise ), }; }; - -export const isMonthFromLiquidation = ( - outstandingCtez: number, - target: number, - tezBalance: number, - currentDrift: number, -): boolean => { - return ( - outstandingCtez * - (target / 2 ** 48) * - (1 + currentDrift / 2 ** 48) ** ((365.25 * 24 * 3600) / 12) * - (16 / 15) > - tezBalance - ); -}; diff --git a/frontend/app/src/components/OvenActions/MintOrBurn.tsx b/frontend/app/src/components/OvenActions/MintOrBurn.tsx index 20f2928c..739f95b5 100644 --- a/frontend/app/src/components/OvenActions/MintOrBurn.tsx +++ b/frontend/app/src/components/OvenActions/MintOrBurn.tsx @@ -13,7 +13,7 @@ import { CTezIcon } from '../CTezIcon/CTezIcon'; import { getOvenMaxCtez } from '../../utils/ovenUtils'; import Typography from '../Typography'; import { logger } from '../../utils/logger'; -import { isMonthFromLiquidation } from '../../api/contracts'; +import { isMonthFromLiquidation } from '../../utils/cfmmUtils'; interface MintOrBurnProps { type: 'mint' | 'repay'; diff --git a/frontend/app/src/pages/MyOvenPage/index.tsx b/frontend/app/src/pages/MyOvenPage/index.tsx index 74e955c5..9c00090e 100644 --- a/frontend/app/src/pages/MyOvenPage/index.tsx +++ b/frontend/app/src/pages/MyOvenPage/index.tsx @@ -18,8 +18,8 @@ import { toSerializeableOven, } from '../../utils/ovenUtils'; import { useCtezBaseStats, useOvenData } from '../../api/queries'; -import { isMonthFromLiquidation } from '../../api/contracts'; import { CTEZ_ADDRESS } from '../../utils/globals'; +import { isMonthFromLiquidation } from '../../utils/cfmmUtils'; export const MyOvenPage: React.FC = () => { const { t } = useTranslation(['common', 'header']); diff --git a/frontend/app/src/utils/cfmmUtils.ts b/frontend/app/src/utils/cfmmUtils.ts index 4483b1e8..67188aff 100644 --- a/frontend/app/src/utils/cfmmUtils.ts +++ b/frontend/app/src/utils/cfmmUtils.ts @@ -61,3 +61,28 @@ export const tradeDCashForDTez = ( } return dtezApprox; }; + +export const calculateMarginalPrice = (tez: number, cash: number, target: number): number => { + const x = cash * target; + const y = tez * 2 ** 48; + const x2 = x * x; + const y2 = y * y; + const nom = tez * (3 * x2 + y2); + const denom = cash * (3 * y2 + x2); + return (nom * 2 ** 48) / denom / 2 ** 48; +}; + +export const isMonthFromLiquidation = ( + outstandingCtez: number, + target: number, + tezBalance: number, + currentDrift: number, +): boolean => { + return ( + outstandingCtez * + (target / 2 ** 48) * + (1 + currentDrift / 2 ** 48) ** ((365.25 * 24 * 3600) / 12) * + (16 / 15) > + tezBalance + ); +}; From d6a72744c51ac65cf046e3fdfdae8d0d19ff106d Mon Sep 17 00:00:00 2001 From: shekhar-shubhendu Date: Tue, 14 Sep 2021 16:38:52 +0200 Subject: [PATCH 69/92] fix: use same value as ctez --- cfmm_initial_storage.mligo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfmm_initial_storage.mligo b/cfmm_initial_storage.mligo index 87ae6427..6033f01e 100644 --- a/cfmm_initial_storage.mligo +++ b/cfmm_initial_storage.mligo @@ -1,6 +1,6 @@ { tezPool = 1n ; cashPool = 1n ; - target = 1n; + target = Bitwise.shift_left 1n 48n; lqtTotal = 1n ; ctez_address = ("CTEZ_ADDRESS" : address) ; cashAddress = ("FA12_CTEZ" : address) ; From f48444370fbe2a90af47cf0327a0ed3ee80c840f Mon Sep 17 00:00:00 2001 From: Arthur Breitmam Date: Thu, 16 Sep 2021 11:23:48 +0100 Subject: [PATCH 70/92] add the old preprocessed version of the tez ctez cfmm for easy comparison --- attic/cfmm_tez_ctez.old.preprocessed.mligo | 569 +++++++++++++++++++++ 1 file changed, 569 insertions(+) create mode 100644 attic/cfmm_tez_ctez.old.preprocessed.mligo diff --git a/attic/cfmm_tez_ctez.old.preprocessed.mligo b/attic/cfmm_tez_ctez.old.preprocessed.mligo new file mode 100644 index 00000000..140ac622 --- /dev/null +++ b/attic/cfmm_tez_ctez.old.preprocessed.mligo @@ -0,0 +1,569 @@ +[@inline] let const_fee = 9995n (* 0.05% fee *) +[@inline] let const_fee_denom = 10000n + +(* Pick one of CASH_IS_TEZ, CASH_IS_FA2, CASH_IS_FA12. tokenToToken isn't supported for CASH_IS_FA12 *) +//#define CASH_IS_TEZ +//#define CASH_IS_FA2 +//#define CASH_IS_FA12 + +(* If the token uses the fa2 standard *) +//#define TOKEN_IS_FA2 +(* To support baking *) +//#define HAS_BAKER +(* To push prices to some consumer contract once per block *) +//#define ORACLE + +(* ============================================================================ + * Entrypoints + * ============================================================================ *) + +type add_liquidity = + [@layout:comb] + { owner : address ; (* address that will own the minted lqt *) + minLqtMinted : nat ; (* minimum number of lqt that must be minter *) + maxTokensDeposited : nat ; (* maximum number of tokens that may be deposited *) + + deadline : timestamp ; (* time before which the request must be completed *) + } + +type remove_liquidity = + [@layout:comb] + { [@annot:to] to_ : address ; (* recipient of the liquidity redemption *) + lqtBurned : nat ; (* amount of lqt owned by sender to burn *) + minCashWithdrawn : nat ; (* minimum amount of cash to withdraw *) + minTokensWithdrawn : nat ; (* minimum amount of tokens to withdraw *) + deadline : timestamp ; (* time before which the request must be completed *) + } + +type cash_to_token = + [@layout:comb] + { [@annot:to] to_ : address ; (* where to send the tokens *) + minTokensBought : nat ; (* minimum amount of tokens that must be bought *) + + deadline : timestamp ; (* time before which the request must be completed *) + } + +type token_to_cash = + [@layout:comb] + { [@annot:to] to_ : address ; (* where to send the cash *) + tokensSold : nat ; (* how many tokens are being sold *) + minCashBought : nat ; (* minimum amount of cash desired *) + deadline : timestamp ; (* time before which the request must be completed *) + } + +type token_to_token = + [@layout:comb] + { outputCfmmContract : address ; (* other cfmm contract *) + minTokensBought : nat ; (* minimum amount of tokens bought *) + [@annot:to] to_ : address ; (* where to send the output tokens *) + tokensSold : nat ; (* amount of tokens to sell *) + deadline : timestamp ; (* time before which the request must be completed *) + } + +(* getbalance update types for fa12 and fa2 *) +type update_fa12_pool = nat +type update_fa2_pool = ((address * nat) * nat) list + +type update_token_pool_internal = update_fa12_pool + +type entrypoint = +| AddLiquidity of add_liquidity +| RemoveLiquidity of remove_liquidity +| CashToToken of cash_to_token +| TokenToCash of token_to_cash +| TokenToToken of token_to_token +| UpdatePools of unit +| UpdateTokenPoolInternal of update_token_pool_internal +| SetLqtAddress of address + +(* ============================================================================= + * Storage + * ============================================================================= *) + +type storage = + [@layout:comb] + { tokenPool : nat ; + cashPool : nat ; + lqtTotal : nat ; + pendingPoolUpdates : nat ; + tokenAddress : address ; + lqtAddress : address ; + lastOracleUpdate : timestamp ; + consumerEntrypoint : address ; + } + +(* Type Synonyms *) + +type result = operation list * storage + +(* FA2 *) +type token_id = nat +type balance_of = ((address * token_id) list * ((((address * nat) * nat) list) contract)) +(* FA1.2 *) +type get_balance = address * (nat contract) + +(* FA1.2 *) +type token_contract_transfer = address * (address * nat) + +(* FA12 *) +type cash_contract_transfer = address * (address * nat) + +(* custom entrypoint for LQT FA1.2 *) +type mintOrBurn = + [@layout:comb] + { quantity : int ; + target : address } + +(* ============================================================================= + * Error codes + * ============================================================================= *) + +[@inline] let error_TOKEN_CONTRACT_MUST_HAVE_A_TRANSFER_ENTRYPOINT = 0n +[@inline] let error_ASSERTION_VIOLATED_CASH_BOUGHT_SHOULD_BE_LESS_THAN_CASHPOOL = 1n +[@inline] let error_PENDING_POOL_UPDATES_MUST_BE_ZERO = 2n +[@inline] let error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE = 3n +[@inline] let error_MAX_TOKENS_DEPOSITED_MUST_BE_GREATER_THAN_OR_EQUAL_TO_TOKENS_DEPOSITED = 4n +[@inline] let error_LQT_MINTED_MUST_BE_GREATER_THAN_MIN_LQT_MINTED = 5n +(* 6n *) +[@inline] let error_ONLY_NEW_MANAGER_CAN_ACCEPT = 7n +[@inline] let error_CASH_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_CASH_BOUGHT = 8n +[@inline] let error_INVALID_TO_ADDRESS = 9n +[@inline] let error_AMOUNT_MUST_BE_ZERO = 10n +[@inline] let error_THE_AMOUNT_OF_CASH_WITHDRAWN_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_CASH_WITHDRAWN = 11n +[@inline] let error_LQT_CONTRACT_MUST_HAVE_A_MINT_OR_BURN_ENTRYPOINT = 12n +[@inline] let error_THE_AMOUNT_OF_TOKENS_WITHDRAWN_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_TOKENS_WITHDRAWN = 13n +[@inline] let error_CANNOT_BURN_MORE_THAN_THE_TOTAL_AMOUNT_OF_LQT = 14n +[@inline] let error_TOKEN_POOL_MINUS_TOKENS_WITHDRAWN_IS_NEGATIVE = 15n +[@inline] let error_CASH_POOL_MINUS_CASH_WITHDRAWN_IS_NEGATIVE = 16n +[@inline] let error_CASH_POOL_MINUS_CASH_BOUGHT_IS_NEGATIVE = 17n +[@inline] let error_TOKENS_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_TOKENS_BOUGHT = 18n +[@inline] let error_TOKEN_POOL_MINUS_TOKENS_BOUGHT_IS_NEGATIVE = 19n +[@inline] let error_ONLY_MANAGER_CAN_SET_BAKER = 20n +[@inline] let error_ONLY_MANAGER_CAN_SET_MANAGER = 21n +[@inline] let error_BAKER_PERMANENTLY_FROZEN = 22n +[@inline] let error_LQT_ADDRESS_ALREADY_SET = 24n +[@inline] let error_CALL_NOT_FROM_AN_IMPLICIT_ACCOUNT = 25n +(* 26n *) +(* 27n *) + +[@inline] let error_INVALID_FA12_TOKEN_CONTRACT_MISSING_GETBALANCE = 28n + +[@inline] let error_THIS_ENTRYPOINT_MAY_ONLY_BE_CALLED_BY_GETBALANCE_OF_TOKENADDRESS = 29n +[@inline] let error_INVALID_FA2_BALANCE_RESPONSE = 30n +[@inline] let error_INVALID_INTERMEDIATE_CONTRACT = 31n + +[@inline] let error_CANNOT_GET_CFMM_PRICE_ENTRYPOINT_FROM_CONSUMER = 35n + +(* ============================================================================= + * Constants + * ============================================================================= *) + + [@inline] let null_address = ("tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" : address) + +(* ============================================================================= + * Functions + * ============================================================================= *) + +(* this is slightly inefficient to inline, but, nice to have a clean stack for + the entrypoints for the Coq verification *) +[@inline] +let mutez_to_natural (a: tez) : nat = a / 1mutez + +[@inline] +let natural_to_mutez (a: nat): tez = a * 1mutez + +[@inline] +let is_a_nat (i : int) : nat option = Michelson.is_nat i + +let ceildiv (numerator : nat) (denominator : nat) : nat = abs ((- numerator) / (int denominator)) + +[@inline] +let mint_or_burn (storage : storage) (target : address) (quantity : int) : operation = + (* Returns an operation that mints or burn lqt from the lqt FA1.2 contract. A negative quantity + corresponds to a burn, a positive one to a mint. *) + let lqt_admin : mintOrBurn contract = + match (Tezos.get_entrypoint_opt "%mintOrBurn" storage.lqtAddress : mintOrBurn contract option) with + | None -> (failwith error_LQT_CONTRACT_MUST_HAVE_A_MINT_OR_BURN_ENTRYPOINT : mintOrBurn contract) + | Some contract -> contract in + Tezos.transaction {quantity = quantity ; target = target} 0mutez lqt_admin + +[@inline] +let token_transfer (storage : storage) (from : address) (to_ : address) (token_amount : nat) : operation = + (* Returns an operation that transfers tokens between from and to. *) + let token_contract: token_contract_transfer contract = + match (Tezos.get_entrypoint_opt "%transfer" storage.tokenAddress : token_contract_transfer contract option) with + | None -> (failwith error_TOKEN_CONTRACT_MUST_HAVE_A_TRANSFER_ENTRYPOINT : token_contract_transfer contract) + | Some contract -> contract in + + Tezos.transaction (from, (to_, token_amount)) 0mutez token_contract + +[@inline] + +let cash_transfer (to_ : address) (cash_amount : nat) : operation= + (* Cash transfer operation, in the case where CASH_IS_TEZ *) + let to_contract : unit contract = + match (Tezos.get_contract_opt to_ : unit contract option) with + | None -> (failwith error_INVALID_TO_ADDRESS : unit contract) + | Some c -> c in + Tezos.transaction () (natural_to_mutez cash_amount) to_contract + +(* ============================================================================= + * Entrypoint Functions + * ============================================================================= *) + +(* We assume the contract is originated with at least one liquidity + * provider set up already, so lqtTotal, xtzPool and cashPool will + * always be positive after the initial setup, unless all liquidity is + * removed, at which point the contract is considered dead and stops working + * properly. If this is a concern, at least one address should keep at least a + * very small amount of liquidity in the contract forever. *) + +let add_liquidity (param : add_liquidity) (storage: storage) : result = + (* Adds liquidity to the contract, mints lqt in exchange for the deposited liquidity. *) + let { + owner = owner ; + minLqtMinted = minLqtMinted ; + maxTokensDeposited = maxTokensDeposited ; + + deadline = deadline } = param in + + let cashDeposited = mutez_to_natural Tezos.amount in + + if storage.pendingPoolUpdates > 0n then + (failwith error_PENDING_POOL_UPDATES_MUST_BE_ZERO : result) + else if Tezos.now >= deadline then + (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) + else + (* The contract is initialized, use the existing exchange rate + mints nothing if the contract has been emptied, but that's OK *) + let cashPool : nat = storage.cashPool in + let lqt_minted : nat = cashDeposited * storage.lqtTotal / cashPool in + let tokens_deposited : nat = ceildiv (cashDeposited * storage.tokenPool) cashPool in + + if tokens_deposited > maxTokensDeposited then + (failwith error_MAX_TOKENS_DEPOSITED_MUST_BE_GREATER_THAN_OR_EQUAL_TO_TOKENS_DEPOSITED : result) + else if lqt_minted < minLqtMinted then + (failwith error_LQT_MINTED_MUST_BE_GREATER_THAN_MIN_LQT_MINTED : result) + else + let storage = {storage with + lqtTotal = storage.lqtTotal + lqt_minted ; + tokenPool = storage.tokenPool + tokens_deposited ; + cashPool = storage.cashPool + cashDeposited} in + + (* send tokens from sender to self *) + let op_token = token_transfer storage Tezos.sender Tezos.self_address tokens_deposited in + + (* mint lqt tokens for them *) + let op_lqt = mint_or_burn storage owner (int lqt_minted) in + + ([op_token; + + op_lqt], storage) + +let remove_liquidity (param : remove_liquidity) (storage : storage) : result = + (* Removes liquidity to the contract by burning lqt. *) + let { to_ = to_ ; + lqtBurned = lqtBurned ; + minCashWithdrawn = minCashWithdrawn ; + minTokensWithdrawn = minTokensWithdrawn ; + deadline = deadline } = param in + + if storage.pendingPoolUpdates > 0n then + (failwith error_PENDING_POOL_UPDATES_MUST_BE_ZERO : result) + else if Tezos.now >= deadline then + (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) + else if Tezos.amount > 0mutez then + (failwith error_AMOUNT_MUST_BE_ZERO : result) + else begin + let cash_withdrawn : nat = (lqtBurned * storage.cashPool) / storage.lqtTotal in + let tokens_withdrawn : nat = (lqtBurned * storage.tokenPool) / storage.lqtTotal in + + (* Check that minimum withdrawal conditions are met *) + if cash_withdrawn < minCashWithdrawn then + (failwith error_THE_AMOUNT_OF_CASH_WITHDRAWN_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_CASH_WITHDRAWN : result) + else if tokens_withdrawn < minTokensWithdrawn then + (failwith error_THE_AMOUNT_OF_TOKENS_WITHDRAWN_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_TOKENS_WITHDRAWN : result) + (* Proceed to form the operations and update the storage *) + else begin + (* calculate lqtTotal, convert int to nat *) + let new_lqtTotal = match (is_a_nat ( storage.lqtTotal - lqtBurned)) with + (* This check should be unecessary, the fa12 logic normally takes care of it *) + | None -> (failwith error_CANNOT_BURN_MORE_THAN_THE_TOTAL_AMOUNT_OF_LQT : nat) + | Some n -> n in + (* Calculate tokenPool, convert int to nat *) + let new_tokenPool = match is_a_nat (storage.tokenPool - tokens_withdrawn) with + | None -> (failwith error_TOKEN_POOL_MINUS_TOKENS_WITHDRAWN_IS_NEGATIVE : nat) + | Some n -> n in + let new_cashPool = match is_nat (storage.cashPool - cash_withdrawn) with + | None -> (failwith error_CASH_POOL_MINUS_CASH_WITHDRAWN_IS_NEGATIVE : nat) + | Some n -> n in + let op_lqt = mint_or_burn storage Tezos.sender (0 - lqtBurned) in + let op_token = token_transfer storage Tezos.self_address Tezos.sender tokens_withdrawn in + + let op_cash = cash_transfer to_ cash_withdrawn in + + let storage = {storage with cashPool = new_cashPool ; lqtTotal = new_lqtTotal ; tokenPool = new_tokenPool} in + ([op_lqt; op_token; op_cash], storage) + end + end + +let cash_to_token (param : cash_to_token) (storage : storage) = + let { to_ = to_ ; + minTokensBought = minTokensBought ; + + deadline = deadline } = param in + + let cashSold = mutez_to_natural Tezos.amount in + + if storage.pendingPoolUpdates > 0n then + (failwith error_PENDING_POOL_UPDATES_MUST_BE_ZERO : result) + else if Tezos.now >= deadline then + (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) + else begin + (* We don't check that xtzPool > 0, because that is impossible + unless all liquidity has been removed. *) + let cashPool = storage.cashPool in + let tokens_bought = + (let bought = (cashSold * const_fee * storage.tokenPool) / (cashPool * const_fee_denom + (cashSold * const_fee)) in + if bought < minTokensBought then + (failwith error_TOKENS_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_TOKENS_BOUGHT : nat) + else + bought) + in + let new_tokenPool = (match is_nat (storage.tokenPool - tokens_bought) with + | None -> (failwith error_TOKEN_POOL_MINUS_TOKENS_BOUGHT_IS_NEGATIVE : nat) + | Some difference -> difference) in + + (* Update cashPool. *) + let storage = { storage with cashPool = storage.cashPool + cashSold ; tokenPool = new_tokenPool } in + (* Send cash from sender to self. *) + + (* Send tokens_withdrawn from exchange to sender. *) + let op_token = token_transfer storage Tezos.self_address to_ tokens_bought in + ([ + + op_token], storage) + end + +let token_to_cash (param : token_to_cash) (storage : storage) = + (* Accepts a payment in token and sends cash. *) + let { to_ = to_ ; + tokensSold = tokensSold ; + minCashBought = minCashBought ; + deadline = deadline } = param in + + if storage.pendingPoolUpdates > 0n then + (failwith error_PENDING_POOL_UPDATES_MUST_BE_ZERO : result) + else if Tezos.now >= deadline then + (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) + else if Tezos.amount > 0mutez then + (failwith error_AMOUNT_MUST_BE_ZERO : result) + else + (* We don't check that tokenPool > 0, because that is impossible + unless all liquidity has been removed. *) + let cash_bought = + let bought = ((tokensSold * const_fee * storage.cashPool) / (storage.tokenPool * const_fee_denom + (tokensSold * const_fee))) in + if bought < minCashBought then (failwith error_CASH_BOUGHT_MUST_BE_GREATER_THAN_OR_EQUAL_TO_MIN_CASH_BOUGHT : nat) else bought in + + let op_token = token_transfer storage Tezos.sender Tezos.self_address tokensSold in + + let op_cash = cash_transfer to_ cash_bought in + + let new_cashPool = match is_nat (storage.cashPool - cash_bought) with + | None -> (failwith error_ASSERTION_VIOLATED_CASH_BOUGHT_SHOULD_BE_LESS_THAN_CASHPOOL : nat) + | Some n -> n in + let storage = {storage with tokenPool = storage.tokenPool + tokensSold ; + cashPool = new_cashPool} in + ([op_token; op_cash], storage) + +let default_ (storage : storage) : result = +(* Entrypoint to allow depositing tez. *) + + (* update cashPool *) + if storage.pendingPoolUpdates > 0n then + (failwith error_PENDING_POOL_UPDATES_MUST_BE_ZERO: result) + else + let storage = {storage with cashPool = storage.cashPool + mutez_to_natural Tezos.amount } in + (([] : operation list), storage) + +let set_lqt_address (lqtAddress : address) (storage : storage) : result = + if storage.pendingPoolUpdates > 0n then + (failwith error_PENDING_POOL_UPDATES_MUST_BE_ZERO : result) + else if Tezos.amount > 0mutez then + (failwith error_AMOUNT_MUST_BE_ZERO : result) + else if storage.lqtAddress <> null_address then + (failwith error_LQT_ADDRESS_ALREADY_SET : result) + else + (([] : operation list), {storage with lqtAddress = lqtAddress}) + +let update_pools (storage : storage) : result = + (* Update the token pool and potentially the cash pool if cash is a token. *) + if Tezos.sender <> Tezos.source then + (failwith error_CALL_NOT_FROM_AN_IMPLICIT_ACCOUNT : result) + else if Tezos.amount > 0mutez then + (failwith error_AMOUNT_MUST_BE_ZERO : result) + else + let cfmm_update_token_pool_internal : update_token_pool_internal contract = Tezos.self "%updateTokenPoolInternal" in + + let token_get_balance : get_balance contract = (match + (Tezos.get_entrypoint_opt "%getBalance" storage.tokenAddress : get_balance contract option) with + | None -> (failwith error_INVALID_FA12_TOKEN_CONTRACT_MISSING_GETBALANCE : get_balance contract) + | Some contract -> contract) in + let op = Tezos.transaction (Tezos.self_address, cfmm_update_token_pool_internal) 0mutez token_get_balance in + + let op_list = [ op ] in + + let pendingPoolUpdates = 1n in + + (op_list, {storage with pendingPoolUpdates = pendingPoolUpdates}) + +[@inline] +let update_fa12_pool_internal (pool_update : update_fa12_pool) : nat = + pool_update + +[@inline] +let update_fa2_pool_internal (pool_update : update_fa2_pool) : nat = + (* We trust the FA2 to provide the expected balance. there are no BFS + shenanigans to worry about unless the token contract misbehaves. *) + match pool_update with + | [] -> (failwith error_INVALID_FA2_BALANCE_RESPONSE : nat) + | x :: xs -> x.1 + +let update_token_pool_internal (pool_update : update_token_pool_internal) (storage : storage) : result = + if (storage.pendingPoolUpdates = 0n or Tezos.sender <> storage.tokenAddress) then + (failwith error_THIS_ENTRYPOINT_MAY_ONLY_BE_CALLED_BY_GETBALANCE_OF_TOKENADDRESS : result) + else + + let pool = update_fa12_pool_internal (pool_update) in + + let pendingPoolUpdates = abs (storage.pendingPoolUpdates - 1n) in + (([] : operation list), {storage with tokenPool = pool ; pendingPoolUpdates = pendingPoolUpdates}) + +let token_to_token (param : token_to_token) (storage : storage) : result = + let { outputCfmmContract = outputCfmmContract ; + minTokensBought = minTokensBought ; + to_ = to_ ; + tokensSold = tokensSold ; + deadline = deadline } = param in + + let outputCfmmContract_contract: cash_to_token contract = + (match (Tezos.get_entrypoint_opt "%cashToToken" outputCfmmContract : cash_to_token contract option) with + | None -> (failwith error_INVALID_INTERMEDIATE_CONTRACT : cash_to_token contract) + | Some c -> c) in + + if storage.pendingPoolUpdates > 0n then + (failwith error_PENDING_POOL_UPDATES_MUST_BE_ZERO : result) + else if Tezos.amount > 0mutez then + (failwith error_AMOUNT_MUST_BE_ZERO : result) + else if Tezos.now >= deadline then + (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) + else + (* We don't check that tokenPool > 0, because that is impossible unless all liquidity has been removed. *) + let cash_bought = ((tokensSold * const_fee * storage.cashPool) / (storage.tokenPool * const_fee_denom + (tokensSold * const_fee))) in + let new_cashPool = match is_nat (storage.cashPool - cash_bought) with + | None -> (failwith error_CASH_POOL_MINUS_CASH_BOUGHT_IS_NEGATIVE : nat) + | Some n -> n in + let storage = {storage with tokenPool = storage.tokenPool + tokensSold ; + cashPool = new_cashPool } in + + let op_send_cash_to_output = Tezos.transaction { minTokensBought = minTokensBought ; + deadline = deadline; to_ = to_ } + (natural_to_mutez cash_bought) + outputCfmmContract_contract in + + let op_accept_token_from_sender = token_transfer storage Tezos.sender Tezos.self_address tokensSold in + ([ + + op_send_cash_to_output; op_accept_token_from_sender] , storage) + +(* For the ctez contract only, accepts tez and calls another cfmm for which cash_is_ctez *) + +type tez_to_token = + [@layout:comb] + { outputCfmmContract : address ; (* other cfmm contract *) + minTokensBought : nat ; (* minimum amount of tokens bought *) + [@annot:to] to_ : address ; (* where to send the output tokens *) + deadline : timestamp ; (* time before which the request must be completed *) + } + +type ctez_to_token = + [@layout:comb] + { [@annot:to] to_ : address ; (* where to send the tokens *) + minTokensBought : nat ; (* minimum amount of tokens that must be bought *) + cashSold : nat ; + deadline : timestamp ; + } + +let tez_to_token (param : tez_to_token) (storage : storage) : result = + + let { outputCfmmContract = outputCfmmContract ; + minTokensBought = minTokensBought ; + to_ = to_ ; + deadline = deadline } = param in + + let outputCfmmContract_contract: ctez_to_token contract = + (match (Tezos.get_entrypoint_opt "%cashToToken" outputCfmmContract : ctez_to_token contract option) with + | None -> (failwith error_INVALID_INTERMEDIATE_CONTRACT : ctez_to_token contract) + | Some c -> c) in + + if storage.pendingPoolUpdates > 0n then + (failwith error_PENDING_POOL_UPDATES_MUST_BE_ZERO : result) + else if Tezos.now >= deadline then + (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) + else + let tezSold = Tezos.amount in + (* We don't check that tokenPool > 0, because that is impossible unless all liquidity has been removed. *) + let ctez_bought = ((tezSold * const_fee * storage.tokenPool) / (storage.cashPool * const_fee_denom + (tezSold * const_fee))) in + let new_tokenPool = match is_nat (storage.tokenPool - ctez_bought) with + | None -> (failwith error_TOKEN_POOL_MINUS_TOKENS_BOUGHT_IS_NEGATIVE : nat) + | Some n -> n in + let storage = {storage with tokenPool = storage.tokenPool + tokensSold ; + cashPool = new_cashPool } in + + let op_send_cash_to_output = Tezos.transaction { minTokensBought = minTokensBought ; + deadline = deadline; to_ = to_ } + (natural_to_mutez cash_bought) + outputCfmmContract_contract in + + let op_accept_token_from_sender = token_transfer storage Tezos.sender Tezos.self_address tokensSold in + ([ + + op_send_cash_to_output; op_accept_token_from_sender] , storage) + + + +let update_consumer (operations, storage : result) : result = + if storage.lastOracleUpdate = Tezos.now + then (operations, storage) + else + let consumer = match (Tezos.get_contract_opt storage.consumerEntrypoint : ((nat * nat) contract) option) with + | None -> (failwith error_CANNOT_GET_CFMM_PRICE_ENTRYPOINT_FROM_CONSUMER : (nat * nat) contract) + | Some c -> c in + ((Tezos.transaction (storage.cashPool, storage.tokenPool) 0mutez consumer) :: operations, + {storage with lastOracleUpdate = Tezos.now}) + +(* ============================================================================= + * Main + * ============================================================================= *) + +let main ((entrypoint, storage) : entrypoint * storage) : result = + match entrypoint with + | AddLiquidity param -> + add_liquidity param storage + | RemoveLiquidity param -> + remove_liquidity param storage + | UpdatePools -> + update_pools storage + | CashToToken param -> + update_consumer + (cash_to_token param storage) + | TokenToCash param -> + update_consumer + (token_to_cash param storage) + | TokenToToken param -> + update_consumer + (token_to_token param storage) + | UpdateTokenPoolInternal token_pool -> + update_token_pool_internal token_pool storage + | SetLqtAddress param -> + set_lqt_address param storage \ No newline at end of file From 937c6df6b85007ef145c632631a1b736660b11e4 Mon Sep 17 00:00:00 2001 From: Arthur Breitmam Date: Thu, 16 Sep 2021 11:24:43 +0100 Subject: [PATCH 71/92] add information about the old preprocessed version of the tez ctez cfmm --- cfmm_tez_ctez.mligo | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cfmm_tez_ctez.mligo b/cfmm_tez_ctez.mligo index cff733ea..5003d2b2 100644 --- a/cfmm_tez_ctez.mligo +++ b/cfmm_tez_ctez.mligo @@ -1,5 +1,7 @@ #include "errors.mligo" +(* Check attic/cfmm_tez_ctez.old.preprocessed.mligo to compare with the old version of ctez *) + [@inline] let error_CALLER_MUST_BE_CTEZ = 1000n [@inline] let error_ASSERTION_VIOLATED_TEZ_BOUGHT_SHOULD_BE_LESS_THAN_TEZPOOL = 1001n From 7032290c042236145a7264fd7f1c08be30f8409b Mon Sep 17 00:00:00 2001 From: shekhar-shubhendu Date: Thu, 16 Sep 2021 12:31:31 +0200 Subject: [PATCH 72/92] fix: remove var with incorrect name --- frontend/app/src/pages/BuySell/Conversion.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/app/src/pages/BuySell/Conversion.tsx b/frontend/app/src/pages/BuySell/Conversion.tsx index 07b40fa4..9dfee721 100644 --- a/frontend/app/src/pages/BuySell/Conversion.tsx +++ b/frontend/app/src/pages/BuySell/Conversion.tsx @@ -55,20 +55,19 @@ const ConvertComponent: React.FC = ({ t, formType }) => { const calcMinBuyValue = (slippage: number, amount: number) => { if (cfmmStorage) { const { tezPool, cashPool, target } = cfmmStorage; - const cashSold = amount * 1e6; const bought = formType === 'tezToCtez' ? tradeDTezForDCash( tezPool.toNumber(), cashPool.toNumber(), - cashSold, + amount * 1e6, target.toNumber(), 4, ) : tradeDCashForDTez( tezPool.toNumber(), cashPool.toNumber(), - cashSold, + amount * 1e6, target.toNumber(), 4, ); From 4d7830786521ac87d30c5c1162bf0c06e65c4b5b Mon Sep 17 00:00:00 2001 From: shekhar-shubhendu Date: Thu, 16 Sep 2021 13:54:43 +0200 Subject: [PATCH 73/92] feat: add loading oven --- .../app/src/components/OvenCard/OvenCard.tsx | 44 +++++++++++++++---- frontend/app/src/contracts/ctez.ts | 6 ++- frontend/app/src/contracts/utils.ts | 11 ++++- .../app/src/pages/CreateOven/CreateOven.tsx | 12 +++++ frontend/app/src/pages/MyOvenPage/index.tsx | 20 ++++++++- frontend/app/src/redux/slices/StatsSlice.ts | 10 ++++- 6 files changed, 89 insertions(+), 14 deletions(-) diff --git a/frontend/app/src/components/OvenCard/OvenCard.tsx b/frontend/app/src/components/OvenCard/OvenCard.tsx index 56160359..70f2abcb 100644 --- a/frontend/app/src/components/OvenCard/OvenCard.tsx +++ b/frontend/app/src/components/OvenCard/OvenCard.tsx @@ -1,6 +1,6 @@ import React from 'react'; import styled from '@emotion/styled'; -import { Box, Button, Chip, Grid } from '@material-ui/core'; +import { Avatar, Box, Button, Chip, Grid, Skeleton } from '@material-ui/core'; import { FcImport, FcExport } from 'react-icons/fc'; import Card from '@material-ui/core/Card'; import CardHeader from '@material-ui/core/CardHeader'; @@ -22,6 +22,7 @@ interface OvenCardProps extends Oven { isImported?: boolean; action?: () => void | Promise; removeExternalAction?: () => void | Promise; + isLoading?: boolean; } export const StyledCard = styled(Card)` @@ -53,6 +54,7 @@ const OvenCardComponent: React.FC = ({ isExternal = false, isImported = false, removeExternalAction, + isLoading, }) => { const { t } = useTranslation(['common']); const maxMintableCtez = maxCtez < 0 ? 0 : maxCtez; @@ -62,16 +64,28 @@ const OvenCardComponent: React.FC = ({ - - + isLoading ? ( + + + + ) : ( + + + + ) + } + title={ + isLoading ? ( + + ) : ( +
+ ) } - title={
} subheader={ - {`${t('ovenBalance')}: ${ovenBalance}`} + {isLoading ? : `${t('ovenBalance')}: ${ovenBalance}`} @@ -102,7 +116,11 @@ const OvenCardComponent: React.FC = ({ } /> - + {isLoading ? ( + + ) : ( + + )} {baker && ( @@ -114,13 +132,20 @@ const OvenCardComponent: React.FC = ({ )} - {t('outstandingCTez')}: {outStandingCtez} + {isLoading ? : `${t('outstandingCTez')} : ${outStandingCtez}`} {maxMintableCtez > 0 && ( - {t('currentUtilization')}: {((outStandingCtez / maxMintableCtez) * 100).toFixed(2)}% + {isLoading ? ( + + ) : ( + `${t('currentUtilization')} : ${( + (outStandingCtez / maxMintableCtez) * + 100 + ).toFixed(2)}%` + )} {isMonthAway && ( ⚠️ @@ -137,6 +162,7 @@ const OvenCardComponent: React.FC = ({ disableRipple disableFocusRipple endIcon={} + disabled={isLoading} > {t('actions')} diff --git a/frontend/app/src/contracts/ctez.ts b/frontend/app/src/contracts/ctez.ts index 23dc52ed..6b1f2e6a 100644 --- a/frontend/app/src/contracts/ctez.ts +++ b/frontend/app/src/contracts/ctez.ts @@ -42,14 +42,18 @@ export const create = async ( op: Depositor, allowedDepositors?: string[], amount = 0, + confirmation = 0, + onConfirmation?: () => void | Promise, ): Promise => { const newOvenId = getLastOvenId(userAddress, cTez.address) + 1; const hash = await executeMethod( cTez, 'create', [newOvenId, bakerAddress, op, allowedDepositors], - undefined, + confirmation, amount, + false, + onConfirmation, ); saveLastOven(userAddress, cTez.address, newOvenId); return hash; diff --git a/frontend/app/src/contracts/utils.ts b/frontend/app/src/contracts/utils.ts index a8ec682f..740a505f 100644 --- a/frontend/app/src/contracts/utils.ts +++ b/frontend/app/src/contracts/utils.ts @@ -8,12 +8,21 @@ export const executeMethod = async ( confirmation = 0, amount = 0, mutez = false, + onConfirmation?: () => void | Promise, ): Promise => { const op = await contract.methods[methodName](...args).send({ amount: amount > 0 ? amount : undefined, mutez, }); - confirmation && (await op.confirmation(confirmation)); + if (confirmation > 0) { + op.confirmation(confirmation) + .then(() => { + onConfirmation && onConfirmation(); + }) + .catch(() => { + onConfirmation && onConfirmation(); + }); + } return op.opHash; }; diff --git a/frontend/app/src/pages/CreateOven/CreateOven.tsx b/frontend/app/src/pages/CreateOven/CreateOven.tsx index 624a52a1..63e0da72 100644 --- a/frontend/app/src/pages/CreateOven/CreateOven.tsx +++ b/frontend/app/src/pages/CreateOven/CreateOven.tsx @@ -1,5 +1,7 @@ import React, { useEffect, useState } from 'react'; import { validateAddress } from '@taquito/utils'; +import { useQueryClient } from 'react-query'; +import { useDispatch } from 'react-redux'; import { withTranslation, WithTranslation } from 'react-i18next'; import * as Yup from 'yup'; import styled from '@emotion/styled'; @@ -21,6 +23,7 @@ import { import { FormikRadioGroup } from '../../components/FormikRadioGroup/FormikRadioGroup'; import { logger } from '../../utils/logger'; import { useDelegates } from '../../api/queries'; +import { StatsSlice } from '../../redux/slices/StatsSlice'; interface CreateVaultForm { delegate: string; @@ -35,10 +38,12 @@ const PaperStyled = styled(Paper)` const CreateOvenComponent: React.FC = ({ t }) => { const [{ pkh: userAddress }] = useWallet(); + const queryClient = useQueryClient(); const { data: delegates } = useDelegates(userAddress); const [delegate, setDelegate] = useState(''); const { addToast } = useToasts(); const history = useHistory(); + const dispatch = useDispatch(); const validationSchema = Yup.object().shape({ delegate: Yup.string() .test({ @@ -122,8 +127,15 @@ const CreateOvenComponent: React.FC = ({ t }) => { data.depositorOp, depositors, data.amount, + 1, + () => { + queryClient.invalidateQueries('ovenData'); + dispatch(StatsSlice.actions.setPendingTransaction(false)); + }, ); if (result) { + console.log(result); + dispatch(StatsSlice.actions.setPendingTransaction(true)); addToast(t('txSubmitted'), { appearance: 'success', autoDismiss: true, diff --git a/frontend/app/src/pages/MyOvenPage/index.tsx b/frontend/app/src/pages/MyOvenPage/index.tsx index 9c00090e..1da51863 100644 --- a/frontend/app/src/pages/MyOvenPage/index.tsx +++ b/frontend/app/src/pages/MyOvenPage/index.tsx @@ -1,4 +1,5 @@ import { CircularProgress, Grid, Box } from '@material-ui/core'; +import BigNumber from 'bignumber.js'; import { useSelector, useDispatch } from 'react-redux'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -28,6 +29,7 @@ export const MyOvenPage: React.FC = () => { const originalTarget = useSelector((state: RootState) => Number(state.stats.baseStats?.originalTarget), ); + const pendingTx = useSelector((state: RootState) => state.stats.transactionPending); const { showActions } = useSelector((state: RootState) => state.oven); const [{ pkh: userAddress }] = useWallet(); const { data: ovenData, isLoading } = useOvenData(userAddress, extOvens); @@ -67,6 +69,20 @@ export const MyOvenPage: React.FC = () => { justifyItems="flex-start" spacing={3} > + {pendingTx && ( + + + + )} {ovenData && ovenData.length > 0 && ovenData @@ -128,7 +144,9 @@ export const MyOvenPage: React.FC = () => { )} {!isLoading && !userAddress && {t('signInToSeeOvens')}} - {!isLoading && userAddress && ovenData?.length === 0 && {t('noOvens')}} + {!isLoading && !pendingTx && userAddress && ovenData?.length === 0 && ( + {t('noOvens')} + )} {!isLoading && userAddress && ovenData && ovenData.length > 0 && ( ) => { state.baseStats = action.payload; }, + setPendingTransaction: (state, action: PayloadAction) => { + state.transactionPending = action.payload; + return state; + }, }, }); From f086b54ac60bdf38137608ec1033cfc9bf051711 Mon Sep 17 00:00:00 2001 From: Shubhendu Shekhar <9253059+shekhar-shubhendu@users.noreply.github.com> Date: Thu, 16 Sep 2021 14:13:21 +0200 Subject: [PATCH 74/92] revert: deploy.sh --- deploy.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deploy.sh b/deploy.sh index e8bf550b..8dd93c18 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,15 +1,15 @@ #!/usr/bin/env bash set -x -mkdir -p _build/mockup +mkdir _build # Modify as need to deploy on different networks -TZC="tezos-client --mode client --protocol PtGRANADsDU8R9daYKAgWnQYAJ64omN1o3KMGVCykShA97vQbvV --endpoint https://granadanet.smartpy.io --base-dir _build/mockup" +TZC="../Tezos/tezos/tezos-client --mode mockup --protocol PtEdo2ZkT9oKpimTah6x2embF25oss54njMuPzkJTEi5RqfdZFA --base-dir _build/mockup" -#rm -rf _build/mockup -$TZC import secret key alice unencrypted:edsk3QoqBuvdamxouPhin7swCvkQNgq4jP5KZPbwWNnwdZpSpJiEbq +rm -rf _build/mockup +$TZC create mockup -deployment_key="alice" +deployment_key="bootstrap1" deployment_key_address=`$TZC show address ${deployment_key} | head -n 1 | awk '{print $2}'` DEPLOYMENT_DATE=$(date '+%Y-%m-%d') From 561f4394d0e46db865206e199efaa3323ce67ec6 Mon Sep 17 00:00:00 2001 From: Arthur Breitmam Date: Tue, 21 Sep 2021 10:32:39 +0100 Subject: [PATCH 75/92] fix tezSold in tez_to_token --- attic/cfmm_tez_ctez.old.preprocessed.mligo | 1 - cfmm_tez_ctez.mligo | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/attic/cfmm_tez_ctez.old.preprocessed.mligo b/attic/cfmm_tez_ctez.old.preprocessed.mligo index 140ac622..372e1da2 100644 --- a/attic/cfmm_tez_ctez.old.preprocessed.mligo +++ b/attic/cfmm_tez_ctez.old.preprocessed.mligo @@ -39,7 +39,6 @@ type cash_to_token = [@layout:comb] { [@annot:to] to_ : address ; (* where to send the tokens *) minTokensBought : nat ; (* minimum amount of tokens that must be bought *) - deadline : timestamp ; (* time before which the request must be completed *) } diff --git a/cfmm_tez_ctez.mligo b/cfmm_tez_ctez.mligo index 5003d2b2..d5fd0668 100644 --- a/cfmm_tez_ctez.mligo +++ b/cfmm_tez_ctez.mligo @@ -56,7 +56,6 @@ type tez_to_token = { outputCfmmContract : address ; (* other cfmm contract *) minTokensBought : nat ; (* minimum amount of cash bought *) [@annot:to] to_ : address ; (* where to send the output cash *) - tezSold : nat ; (* amount of tez to sell *) deadline : timestamp ; (* time before which the request must be completed *) rounds : int ; (* number of iterations in estimating the difference equations. Default should be 4n. *) } @@ -415,7 +414,6 @@ let tez_to_token (param : tez_to_token) (storage : storage) : result = let { outputCfmmContract = outputCfmmContract ; minTokensBought = minTokensBought ; to_ = to_ ; - tezSold = tezSold ; deadline = deadline ; rounds = rounds } = param in @@ -427,6 +425,7 @@ let tez_to_token (param : tez_to_token) (storage : storage) : result = if Tezos.now >= deadline then (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) else + let tezSold = mutez_to_natural Tezos.amount in (* We don't check that cashPool > 0, because that is impossible unless all liquidity has been removed. *) let cash_bought = (let bought = trade_dtez_for_dcash storage.tezPool storage.cashPool tezSold storage.target rounds in From 5e60bb58edc49bc7c75d6888d3d0bbede8545a96 Mon Sep 17 00:00:00 2001 From: Georgios Karachalias Date: Fri, 15 Oct 2021 14:52:01 +0200 Subject: [PATCH 76/92] Add entrypoint GetMarginalPrice --- cfmm_tez_ctez.mligo | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cfmm_tez_ctez.mligo b/cfmm_tez_ctez.mligo index d5fd0668..50ebdceb 100644 --- a/cfmm_tez_ctez.mligo +++ b/cfmm_tez_ctez.mligo @@ -64,6 +64,8 @@ type tez_to_token = (* A target t such that t / 2^48 is the target price from the ctez contract *) type ctez_target = nat +(* Marginal price, as a pair containing the numerator and the denominator *) +type get_marginal_price = (nat * nat) contract type entrypoint = | AddLiquidity of add_liquidity @@ -73,6 +75,7 @@ type entrypoint = | CashToTez of cash_to_tez | TezToToken of tez_to_token | SetLqtAddress of address +| GetMarginalPrice of get_marginal_price (* ============================================================================= @@ -465,6 +468,11 @@ let update_consumer (operations, storage : result) : result = ((Tezos.transaction (marginal_price storage.tezPool storage.cashPool storage.target) 0mutez consumer) :: operations, {storage with lastOracleUpdate = Tezos.now}) +let get_marginal_price (param : get_marginal_price) (storage : storage) : result = + let price = marginal_price storage.tezPool storage.cashPool storage.target in + let op = Tezos.transaction price 0mutez param in + ([op], storage) + (* ============================================================================= * Main * ============================================================================= *) @@ -488,3 +496,5 @@ let main ((entrypoint, storage) : entrypoint * storage) : result = (tez_to_token param storage) | SetLqtAddress param -> set_lqt_address param storage + | GetMarginalPrice param -> + get_marginal_price param storage From fe3e88361033676ebd241b695ab258a14ada93c0 Mon Sep 17 00:00:00 2001 From: Arthur Breitman Date: Wed, 17 Nov 2021 14:22:30 +0000 Subject: [PATCH 77/92] Update README.md Explain flat curve --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 986184c4..b0566bff 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,12 @@ Each time the CFMM pushes its rate to the ctez contract, the drift, and the targ If the price of ctez implied by the CFMM is below the target, the drift is *raised* by `max(1024 * (target / price - 1)^2, 1) * 2^(-48)` times the number of seconds since the last adjustment. If it is below, it is *lowered* by that amount. This corresponds roughly to a maximum adjustment of the annualized drift of one percentage point for every fractional day since the last adjustment. The adjustment saturates when the discrepancy exceeds one 32ndth. Note that, by a small miracle, `ln(1.01) / year / day ~ 1.027 * 2^(-48) / second^2` which we use to simplify the computation in the implementation. +### Curve + +If `x` is the quantity of tez, and `y` the quantity of ctez, and `t` the target (in tez per ctez), then CFMM uses the constant formula `x y (x^2 / t + t y^2) = k`. The marginal price is given by `(x^3 + 3 t^2 x y^2) / (t^2 y^3 + 3 x^2 y)`. The price is equal to the target when `y = x / t` and, on that point, the second and third derivative of the curve vanish, meaning there is more liquidity there. + +The target is fed to the CFMM by the ctez contract. + ## Rationale If the price of ctez remains below its target, the drift will keep increasing and at some point, under a quadratically compounding rate vaults are forced into liquidation which may cause ctez to be bid up to claim the tez in the vaults. From ceabe74d05acce511aee3efce00e472f45d70a10 Mon Sep 17 00:00:00 2001 From: Arthur Breitmam Date: Fri, 26 Nov 2021 11:20:29 +0000 Subject: [PATCH 78/92] use a flatter cuve --- README.md | 3 ++- cfmm_tez_ctez.mligo | 36 +++++------------------------------ newton.mligo | 46 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 32 deletions(-) create mode 100644 newton.mligo diff --git a/README.md b/README.md index b0566bff..674d10ba 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,8 @@ If the price of ctez implied by the CFMM is below the target, the drift is *rais ### Curve -If `x` is the quantity of tez, and `y` the quantity of ctez, and `t` the target (in tez per ctez), then CFMM uses the constant formula `x y (x^2 / t + t y^2) = k`. The marginal price is given by `(x^3 + 3 t^2 x y^2) / (t^2 y^3 + 3 x^2 y)`. The price is equal to the target when `y = x / t` and, on that point, the second and third derivative of the curve vanish, meaning there is more liquidity there. +If `x` is the quantity of tez, and `y` the quantity of ctez, and `t` the target (in tez per ctez), then CFMM uses the constant formula `(x + y)^8 - (x - y)^8 = k`. The price is equal to the target when `y = x / t` and, on that point, all derivatives from the 2nd to the 7th vanish, meaning there is more liquidity there. +To give an example, if `t = 1`, `x = 100` and `y = 100`, and a user adds `dx = 63` to the pool, they receive `dy = 62.4`, that is less than `1%` slippage for taking nearly two thirds of the `y` pool! The target is fed to the CFMM by the ctez contract. diff --git a/cfmm_tez_ctez.mligo b/cfmm_tez_ctez.mligo index 50ebdceb..729dd157 100644 --- a/cfmm_tez_ctez.mligo +++ b/cfmm_tez_ctez.mligo @@ -1,4 +1,5 @@ #include "errors.mligo" +#include "newton.mligo" (* Check attic/cfmm_tez_ctez.old.preprocessed.mligo to compare with the old version of ctez *) @@ -170,36 +171,6 @@ let tez_transfer (to_ : address) (tez_amount : nat) : operation= | Some c -> c in Tezos.transaction () (natural_to_mutez tez_amount) to_contract -(* A function to transfer assets while maintaining a constant* isoutility. - * ... or slightly increasing due to loss of precision or incomplete convergence *) - -let rec newton_dx_to_dy_rec (xp, xp2, x3y_plus_y3x, y, dy_approx, rounds : nat * nat * nat * nat * nat * int) : nat = - if rounds <= 0 then - dy_approx - else - let yp = y - dy_approx in - let yp2 = abs (yp * yp) in - (* Newton descent formula *) - (* num is always positive, even without abs which is only there for casting to nat *) - let num = abs (xp * yp * (xp2 + yp2) - x3y_plus_y3x) in - let denom = xp * (xp2 + 3n * yp2) in - let adjust = num / denom in - let new_dy_approx = dy_approx + adjust in - newton_dx_to_dy_rec (xp, xp2, x3y_plus_y3x, y, new_dy_approx, rounds - 1) - (* - if denom = 0, then either: - 1. xp = 0 => x + dx = 0, which we don't allow, or - 2. xp = 0 and yp = 0 => a = 0 and (b = 0 or yp = 0), which implies - that the price target is 0. - *) - -let rec newton_dx_to_dy (x, y, dx, rounds : nat * nat * nat * int) : nat = - let xp = x + dx in - let xp2 = xp * xp in - let x3y_plus_y3x = x * y * (x * x + y * y) in - newton_dx_to_dy_rec (xp, xp2, x3y_plus_y3x, y, 0n, rounds) - - // A function that outputs dy (diff_cash) given x, y, and dx let trade_dtez_for_dcash (tez : nat) (cash : nat) (dtez : nat) (target : nat) (rounds : int) : nat = let x = Bitwise.shift_left tez 48n in @@ -225,12 +196,15 @@ let trade_dcash_for_dtez (tez : nat) (cash : nat) (dcash : nat) (target : nat) ( else dtez_approx + +// Marginal price of cash in tez let marginal_price (tez : nat) (cash : nat) (target : nat) : (nat * nat) = let x = cash * target in let y = Bitwise.shift_left tez 48n in let x2 = x * x in let y2 = y * y in - (tez * (3n * x2 + y2), cash * (3n * y2 + x2)) + let (num, den) = margin x y in (* how many tez do I get for my cash *) + (Bitwise.shift_left num 48n, den * target) (* ============================================================================= * Entrypoint Functions diff --git a/newton.mligo b/newton.mligo new file mode 100644 index 00000000..ac0d7594 --- /dev/null +++ b/newton.mligo @@ -0,0 +1,46 @@ +(* Functions for the isoutility curve (x + y)^8 - (x - y)^8 = k *) + +let util (x: nat) (y: nat) : nat * nat = + let plus = x + y in + let minus = x - y in + let plus_2 = plus * plus in + let plus_4 = plus_2 * plus_2 in + let plus_8 = plus_4 * plus_4 in + let plus_7 = plus_4 * plus_2 * plus in + let minus_2 = minus * minus in + let minus_4 = minus_2 * minus_2 in + let minus_8 = minus_4 * minus_4 in + let minus_7 = minus_4 * minus_2 * minus in + (* minus_7 + plus_7 should always be positive *) + (* since x >0 and y > 0, x + y > x - y and therefore (x + y)^7 > (x - y)^7 and (x + y^7 - (x - y)^7 > 0 *) + (abs (plus_8 - minus_8), 8n * (abs (minus_7 + plus_7))) + +type newton_param = {x : nat ; y : nat ; dx : nat ; dy : nat ; u : nat ; n : int} + +let rec newton (p : newton_param) : nat = + if p.n = 0 then + p.dy + else + let new_u, new_du_dy = util (p.x + p.dx) (abs (p.y - p.dy)) in + (* new_u - p.u > 0 because dy remains an underestimate *) + let dy = p.dy + abs ((new_u - p.u) / new_du_dy) in + (* dy is an underestimate because we start at 0 and the utility curve is convex *) + newton {p with dy = dy ; n = p.n - 1} + +let rec newton_dx_to_dy (x, y, dx, rounds : nat * nat * nat * int) : nat = + let xp = x + dx in + let xp2 = xp * xp in + let u, _ = util x y in + newton {x = x; y = y; dx = dx ; dy = 0n ; u = u; n = rounds} + +let margin (x: nat) (y: nat) : nat * nat = + let plus = x + y in + let minus = x - y in + let plus_2 = plus * plus in + let plus_4 = plus_2 * plus_2 in + let plus_7 = plus_4 * plus_2 * plus in + let minus_2 = minus * minus in + let minus_4 = minus_2 * minus_2 in + let minus_7 = minus_4 * minus_2 * minus in + (* plus_7 - minus_7 is always positive because it's 2 y ( 7 (x^6 + 5 x^4 y^2 + 3 x^2 y^4) + y^6) ) *) + (abs (plus_7 - minus_7), abs (plus_7 + minus_7) ) (* that's (du/dx) / (du/dy) , or how many y I get for one x *) \ No newline at end of file From 7b2955a065b0c9949f349a1fcfe7b28a42fa6dda Mon Sep 17 00:00:00 2001 From: Arthur Breitmam Date: Fri, 26 Nov 2021 11:36:53 +0000 Subject: [PATCH 79/92] remove dead code --- cfmm_tez_ctez.mligo | 2 -- newton.mligo | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/cfmm_tez_ctez.mligo b/cfmm_tez_ctez.mligo index 729dd157..9f23d2b4 100644 --- a/cfmm_tez_ctez.mligo +++ b/cfmm_tez_ctez.mligo @@ -201,8 +201,6 @@ let trade_dcash_for_dtez (tez : nat) (cash : nat) (dcash : nat) (target : nat) ( let marginal_price (tez : nat) (cash : nat) (target : nat) : (nat * nat) = let x = cash * target in let y = Bitwise.shift_left tez 48n in - let x2 = x * x in - let y2 = y * y in let (num, den) = margin x y in (* how many tez do I get for my cash *) (Bitwise.shift_left num 48n, den * target) diff --git a/newton.mligo b/newton.mligo index ac0d7594..74456d32 100644 --- a/newton.mligo +++ b/newton.mligo @@ -43,4 +43,4 @@ let margin (x: nat) (y: nat) : nat * nat = let minus_4 = minus_2 * minus_2 in let minus_7 = minus_4 * minus_2 * minus in (* plus_7 - minus_7 is always positive because it's 2 y ( 7 (x^6 + 5 x^4 y^2 + 3 x^2 y^4) + y^6) ) *) - (abs (plus_7 - minus_7), abs (plus_7 + minus_7) ) (* that's (du/dx) / (du/dy) , or how many y I get for one x *) \ No newline at end of file + (abs (plus_7 - minus_7), abs (plus_7 + minus_7)) (* that's (du/dx) / (du/dy) , or how many y I get for one x *) From 3b41b4f374d95563edbff641af030ab929a9a5ae Mon Sep 17 00:00:00 2001 From: Arthur Breitmam Date: Thu, 2 Dec 2021 14:32:52 +0000 Subject: [PATCH 80/92] add a short description of ctez mechanisms --- description.md | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 description.md diff --git a/description.md b/description.md new file mode 100644 index 00000000..d20066e4 --- /dev/null +++ b/description.md @@ -0,0 +1,83 @@ +# A description of ctez mechanisms + +This is a rough outline of how ctez works in practice, with a few worked out examples. + +## Motivation + +The concept of delegation on Tezos can render certain smart-contracts tricky, especially when assets from multiple participants are pooled together. A wrapped version of tez is useful in those situations, if we can achieve the following properties: + +1. fungibility: those tokens must be interchangeable with each other +2. decentralization and trust minimization: they should not benefit any specific baker, nor leave that option open to any governance body +3. no opportunity cost from missing out on delegation + +It's fairly easy to satisfy two out of three constraints. If we do not care about missing out on delegation, a simple contracts that wraps tez and does not delegate the tez it holds will do the job. If we do not care about fungibility, we can let everyone create their own wrapped token backed by funds they delegated. Lastly, if we do not care about decentralization or trustlessness, we could leave the decision of where to delegate pooled funds to some third party. + +Ctez can achieve all three property with a bit of mathematics! + + ## Glossary + +In order to explain and understand ctez, it first helps to define a precise glossary. + +### Target + +The target represents the target peg between tez and ctez. A target of 1.5 means that the target peg is 1 ctez = 1.5 tez. Inasmuch as the ctez contract "tries" to do anything, it tries to automatically balance economic forces so that ctez is generally exchanged for tez at or around the target peg. The peg is only a target, there is no hard guarantee that, at any given time, ctez can be exchanged for tez at the target peg or vice-versa. + +The target peg starts out at 1.0, meaning the smart-contracts initially attempts to balance out incentives so that 1 tez is exchangeable for 1 ctez, but this changes over time. **This is important**, the target peg between tez and ctez is not fixed, it changes over time. It changes smoothly, predictably, and relatively slowly, but it changes. All else equal, at equilibrium this change may tend to compensate for the opportunity cost of delegation + +### Drift + +**This is the most important number to pay attention to** in ctez, it represents how quickly the target peg between ctez and tez increases or decreases. If the drift is positive, then the target peg is going to increase, if the drift is negative, the target peg is going to decrease. + +### Premium + +The premium is **the second most important number to pay attention to** in ctez. It represents the ratio between the price of ctez in tez, as observed in one specific cfmm contract (a so-called "dex"), and the target. If the premium is negative, then ctez is below its target, if the premium is positive, then it is above its target. + +### Oven + +A smart contract holding tez, which lets its owner mint new ctez. The number of tez in the contract must exceed the number of ctez minted times the target peg, times 16/15. The last factor represents a safety buffer of 6.667%. The total number of ctez minted by an oven is called the outstanding amount of ctez. If ctez is returned to the oven (burned), the outstanding amount decreases (as does the total supply of ctez). + +### Liquidation + +If the drift is positive, the target increases over time. When the target increases, it's possible that some ovens end up having more ctez outstanding than they were allowed to mint in the first place. When that happens, anyone can come in and return ctez to the oven, in exchange for tez. The exchange happens **at the target peg** plus 3.226% (1/31), not at the market price. The entirety of the oven can be liquidated, not just the amount necessary to make it properly collateralized again. Ovens owner can avert liquidation and the associated penalty by returning ctez to the oven ahead of time. Liquidations should never come as a surprise if the oven owners are paying attention. + + +## Economic mechanisms + +So how does ctez maintain, or tries to maintain, a peg close to the target, anyway? When the premium is negative, the drift *increases*. This can seem surprising, if the price is below the target, shouldn't we try to make the target smaller to meet the price by decreasing the drift or even making the drift negative? No, this would just lead to a spiral where it ends up at 0. + +Assume for the moment that ctez generally succeeds in eventually restoring its peg. It's a very big and very important "if", but it will be motivated below. *If* that is the case, a higher drift makes it more attractive to hold ctez and less attractive to have outstanding ctez in an oven, meaning it leads people to buy ctez or to return it to ovens reducing the supply of ctez, which tends to moves the price in the direction of the peg. + +Likewise, if the premium is positive, decreasing the drift makes it less attractive to hold ctez and more attractive to mint it and sell it, which puts downwards pressure on the price of ctez in tez. + +### What if a discount persists for a long time? + +Let's motivate that important "if". Why do we assume that the peg can be eventually restored? So long as ctez trades at a discount, the drift keeps increasing. This means that the target increases more than exponentially, it increases like the exponential of a quadratic function of time! + +To give some concrete numbers, suppose the target starts at 1.0, that the drift starts at 0% per year, and that a premium of at last -3.125% (a discount) persists continuously for a very long time. After one day, the drift will be about 1% per year, after two days, it will be about 2% per year. How does this look for the target over the course of one year? + +It looks like this. +![](https://i.imgur.com/bms5TZo.png) + +After one month, the target would be about `1.012`, after 2 months `1.05`, after 3 months, `1.12`, after 6 months, `1.56`, after a year, `5.85`. + +Still, what prevents everyone from blissfully ignoring the target? In one word: liquidations. Oven owners cannot (and should not) ignore a rising target forever. They cannot outpace the target for very long and, at some point, they will have to either close their oven or be liquidated and, when that happens, it will be at the target price. So ctez holders may not always be able to redeem at the target, but they can wait for the discount to disappear. They could wait for months, but the longer they are forced to wait, the more attractive it becomes. + +If oven owners decided to be very conservative and only mint 1% of the maximum amount of ctez they can mint, it could take 19 to 20 months for the target to rise enough to liquidate them — that's a long time — but at this point the target would be over 100 and liquidation would happen at that price. + +### What if a positive premium persists for a long time + +If a positive premium persists for a long time, then the drift will start going down, and might even become negative. At this point, oven owners could simply mint ctez, sell them for tez, use those to mint more ctez and not have to worry about liquidation because the target is decreasing. If the premium becomes negative, then they can buy back ctez and unwide the position. Even if liquidity is limited, the mere unwinding of their position can prevent the drift from rising until they are fully unwound. + +### A healthy drift + +Of course, the normal state of affair is not one where discounts last for two years and allow ctez to be exchanged at 100x in liquidations. Ctez holders and oven owners can and should react to the drift. As a rule of thumb, the drift should be around the baking reward (about 5.5% as these lines are written). If the drift is higher than this, then it can be more attractive to hold ctez than to hold and delegate tez. If the drift is lower than this, then oven owners can mint ctez and increase their balance via baking rewards faster then the target increases. + +A simple rule for oven owners or ctez holders is to pick the drift they are comfortable with, and to increase or decrease their position depending on what the drift is. + +## How does the drift change exactly? + +The exact formula for the change of the drift is detailed in the github repo for ctez but, overall, when the premium or discount is large (more than 1/32 or 3.125%), then the drift can change by as much as 1%/year every day. When it's in the middle, if follows a quadratic formula. The graph looks like this. + +![](https://i.imgur.com/LhDAEuL.png) + +Where the y axis represents (approximately) how many percentage points per year are added or removed to the drift on a daily basis, and the y axis represents the premium. As we can see, when the premium or discount is small, the drift changes very little. The drift adjustment for a premium or discount of 0.5% is about 40 times smaller than the drift adjustment for a premium or discount of 3.125%. \ No newline at end of file From 8c581d3555d3e6c1db854ea44e9dc4bdaadbf720 Mon Sep 17 00:00:00 2001 From: Arthur B Date: Sun, 27 Nov 2022 14:40:23 -0500 Subject: [PATCH 81/92] add support for liquidity fee --- .ligoproject | 1 + cfmm_initial_storage.ml | 10 +++ cfmm_tez_ctez.ml | 5 ++ cfmm_tez_ctez.mligo | 31 ++++--- ctez.mligo | 63 +++++++++---- ctez_initial_storage.ml | 7 ++ fa12.ml | 166 +++++++++++++++++++++++++++++++++++ fa12.mligo | 13 +++ fa12_ctez_initial_storage.ml | 7 ++ fees.md | 1 + oven.ml | 48 ++++++++++ oven_types.ml | 31 +++++++ 12 files changed, 356 insertions(+), 27 deletions(-) create mode 100644 .ligoproject create mode 100644 cfmm_initial_storage.ml create mode 100644 cfmm_tez_ctez.ml create mode 100644 ctez_initial_storage.ml create mode 100644 fa12.ml create mode 100644 fa12_ctez_initial_storage.ml create mode 100644 fees.md create mode 100644 oven.ml create mode 100644 oven_types.ml diff --git a/.ligoproject b/.ligoproject new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/.ligoproject @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/cfmm_initial_storage.ml b/cfmm_initial_storage.ml new file mode 100644 index 00000000..b55b662a --- /dev/null +++ b/cfmm_initial_storage.ml @@ -0,0 +1,10 @@ +{ tokenPool = 1n ; + cashPool = 1n ; + lqtTotal = 1n ; + pendingPoolUpdates = 0n ; + tokenAddress = ("FA12_CTEZ" : address) ; + lqtAddress = ("tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" : address) ; + lastOracleUpdate = ("2021-01-01T00:00:00Z" : timestamp) ; + consumerEntrypoint = ("CTEZ_ADDRESS%cfmm_price" : address) ; + } + diff --git a/cfmm_tez_ctez.ml b/cfmm_tez_ctez.ml new file mode 100644 index 00000000..83088093 --- /dev/null +++ b/cfmm_tez_ctez.ml @@ -0,0 +1,5 @@ +#define ORACLE +#define CASH_IS_TEZ +[@inline] let const_fee = 9995n (* 0.05% fee *) +[@inline] let const_fee_denom = 10000n +#include "cfmm.mligo" \ No newline at end of file diff --git a/cfmm_tez_ctez.mligo b/cfmm_tez_ctez.mligo index 9f23d2b4..b7cda3b2 100644 --- a/cfmm_tez_ctez.mligo +++ b/cfmm_tez_ctez.mligo @@ -62,8 +62,8 @@ type tez_to_token = } -(* A target t such that t / 2^48 is the target price from the ctez contract *) -type ctez_target = nat +(* A target t such that t / 2^48 is the target price from the ctez contract, and the number of newly minted ctez *) +type ctez_target = nat * nat (* Marginal price, as a pair containing the numerator and the denominator *) type get_marginal_price = (nat * nat) contract @@ -123,9 +123,10 @@ type mintOrBurn = * ============================================================================= *) [@inline] let null_address = ("tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" : address) -[@inline] let const_fee = 9995n (* 0.05% fee *) +[@inline] let const_fee = 9999n (* 0.01% fee *) [@inline] let const_fee_denom = 10000n + (* ============================================================================= * Functions * ============================================================================= *) @@ -204,8 +205,17 @@ let marginal_price (tez : nat) (cash : nat) (target : nat) : (nat * nat) = let (num, den) = margin x y in (* how many tez do I get for my cash *) (Bitwise.shift_left num 48n, den * target) +(* ======== + * Views + *) + + [@view] let cashPool ((), s : unit * storage) : nat = s.cashPool + [@view] let tezPool ((), s : unit * storage) : nat = s.tezPool + [@view] let storage ((), s: unit * storage) : storage = s + + (* ============================================================================= - * Entrypoint Functions + * Entrypoint Functions· * ============================================================================= *) (* We assume the contract is originated with at least one liquidity @@ -291,12 +301,12 @@ let remove_liquidity (param : remove_liquidity) (storage : storage) : result = end end -let ctez_target (param : ctez_target) (storage : storage) = +let ctez_target ((target, minted): ctez_target) (storage : storage) = if Tezos.sender <> storage.ctez_address then (failwith error_CALLER_MUST_BE_CTEZ : result) else - let updated_target = param in - let storage = {storage with target = updated_target} in + let updated_target = target in + let storage = {storage with target = updated_target ; cashPool = storage.cashPool + minted} in (([] : operation list), storage) @@ -433,11 +443,10 @@ let update_consumer (operations, storage : result) : result = if storage.lastOracleUpdate = Tezos.now then (operations, storage) else - let consumer = match (Tezos.get_contract_opt storage.consumerEntrypoint : ((nat * nat) contract) option) with -// TODO : when ligo is fixed let consumer = match (Tezos.get_entrypoint_opt "cfmm_price" storage.consumerEntrypoint : ((nat * nat) contract) option) with - | None -> (failwith error_CANNOT_GET_CFMM_PRICE_ENTRYPOINT_FROM_CONSUMER : (nat * nat) contract) + let consumer = match (Tezos.get_contract_opt storage.consumerEntrypoint : (((nat * nat) * nat) contract) option) with + | None -> (failwith error_CANNOT_GET_CFMM_PRICE_ENTRYPOINT_FROM_CONSUMER : ((nat * nat) * nat) contract) | Some c -> c in - ((Tezos.transaction (marginal_price storage.tezPool storage.cashPool storage.target) 0mutez consumer) :: operations, + ((Tezos.transaction ((marginal_price storage.tezPool storage.cashPool storage.target), storage.cashPool) 0mutez consumer) :: operations, {storage with lastOracleUpdate = Tezos.now}) let get_marginal_price (param : get_marginal_price) (storage : storage) : result = diff --git a/ctez.mligo b/ctez.mligo index 604c93ac..87603be8 100644 --- a/ctez.mligo +++ b/ctez.mligo @@ -25,20 +25,22 @@ type parameter = | Liquidate of liquidate | Register_deposit of register_deposit | Mint_or_burn of mint_or_burn - | Cfmm_price of nat * nat + | Cfmm_info of nat * nat * nat | Set_addresses of set_addresses | Get_target of nat contract -type oven = {tez_balance : tez ; ctez_outstanding : nat ; address : address} +type oven = {tez_balance : tez ; ctez_outstanding : nat ; address : address ; fee_index : nat} type storage = { ovens : (oven_handle, oven) big_map ; target : nat ; drift : int ; - last_drift_update : timestamp ; + last_update : timestamp ; ctez_fa12_address : address ; (* address of the fa12 contract managing the ctez token *) cfmm_address : address ; (* address of the cfmm providing the price feed *) + fee_index : nat ; } + type result = (operation list) * storage (* Errors *) @@ -66,9 +68,11 @@ type result = (operation list) * storage let get_oven (handle : oven_handle) (s : storage) : oven = match Big_map.find_opt handle s.ovens with | None -> (failwith error_OVEN_DOESNT_EXIST : oven) - | Some o -> o + | Some oven -> (* Adjust the amount of outstanding ctez in the oven, record the fee index at that time. *) + let ctez_outstanding = abs((- oven.ctez_outstanding * oven.fee_index) / s.fee_index) in + {oven with fee_index = fee_index ; ctez_outstanding = ctez_outstanding} -let is_under_collateralized (oven : oven) (target : nat): bool = +let is_under_collateralized (oven : oven) (target : nat) (fee_index : nat): bool = (15n * oven.tez_balance) < (Bitwise.shift_right (oven.ctez_outstanding * target) 44n) * 1mutez let get_oven_withdraw (oven_address : address) : (tez * (unit contract)) contract = @@ -87,8 +91,11 @@ let get_ctez_mint_or_burn (fa12_address : address) : (int * address) contract = | Some c -> c -(* Entrypoint Functions *) +(* Views *) +[@view] let get_fee_index ((), s: unit * storage) : nat = s.fee_index +[@view] let get_target ((), s : unit * storage) : nat = s.target +(* Entrypoint Functions *) let create (s : storage) (create : create) : result = let handle = { id = create.id ; owner = Tezos.sender } in if Big_map.mem handle s.ovens then @@ -96,7 +103,7 @@ let create (s : storage) (create : create) : result = else let (origination_op, oven_address) : operation * address = create_oven create.delegate Tezos.amount { admin = Tezos.self_address ; handle = handle ; depositors = create.depositors } in - let oven = {tez_balance = Tezos.amount ; ctez_outstanding = 0n ; address = oven_address} in + let oven = {tez_balance = Tezos.amount ; ctez_outstanding = 0n ; address = oven_address ; fee_index = s.fee_index} in let ovens = Big_map.update handle (Some oven) s.ovens in ([origination_op], {s with ovens = ovens}) @@ -174,11 +181,13 @@ let mint_or_burn (s : storage) (p : mint_or_burn) : result = let get_target (storage : storage) (callback : nat contract) : result = ([Tezos.transaction storage.target 0mutez callback], storage) -let cfmm_price (storage : storage) (price_numerator : nat) (price_denominator : nat) : result = + +let cfmm_info (storage : storage) (price_numerator : nat) (price_denominator : nat) (cash_pool : nat): result = if Tezos.sender <> storage.cfmm_address then (failwith error_CALLER_MUST_BE_CFMM : result) else - let delta = abs (Tezos.now - storage.last_drift_update) in + (* get the new target *) + let delta = abs (Tezos.now - storage.last_update) in let target = storage.target in let d_target = Bitwise.shift_right (target * (abs storage.drift) * delta) 48n in (* We assume that `target - d_target < 0` never happens for economic reasons. @@ -199,29 +208,51 @@ let cfmm_price (storage : storage) (price_numerator : nat) (price_denominator : let p2 = price * price in if x > p2 then delta else x * delta / p2 in + (* set new drift *) let drift = if target_less_price > 0 then storage.drift + d_drift else storage.drift - d_drift in + + (* Compute what the liquidity fee shoud be, based on the ratio of total outstanding ctez to ctez in cfmm *) + let outstanding = ( + match (Tezos.call_view "total_supply" () storage.fa12_address) with + | None -> (failwith unit : nat) + | Some n-> n + ) in + (* fee_r is given as a multiple of 2^(-48)... note that 2^(-27) Np / s ~ 0.98 cNp / year, so roughly a max of 1% / year *) + let fee_r = if 16n * cashPool < outstanding then 2097152n else if 8n * cashPool > outstanding then 2097152n else + (Bitwise.shift_left (outstanding - 8 * cashPool) 22) / (outstanding) in + + let new_fee_index = storage.fee_index + Bitwise.shift_right (delta * storage.fee_index * fee_r) 48 in + (* Compute how many ctez have implicitly been minted since the last update *) + (* We round this down while we round the ctez owed up. This leads, over time, to slightly overestimating the outstanding ctez, which is conservative. *) + let minted = outstanding * (abs (new_fee_index - storage.fee_index)) / storage.fee_index in + + (* Create the operation to explicitly mint the ctez in the FA12 contract, and credit it to the CFMM *) + let ctez_mint_or_burn = get_ctez_mint_or_burn storage.ctez_fa12_address in + let op_mint_ctez = Tezos.transaction (minted, storage.cfmm_address) 0mutez ctez_mint_or_burn in + (* update the cfmm with a new target and mention its cashPool increase *) + let cfmm_address = storage.cfmm_address in - let txndata_ctez_target = target in let entrypoint_ctez_target = - (match (Tezos.get_entrypoint_opt "%ctezTarget" cfmm_address : nat contract option) with - | None -> (failwith error_INVALID_CTEZ_TARGET_ENTRYPOINT : nat contract) + (match (Tezos.get_entrypoint_opt "%ctezTarget" cfmm_address : (nat * nat) contract option) with + | None -> (failwith error_INVALID_CTEZ_TARGET_ENTRYPOINT : (nat * nat) contract) | Some c -> c ) in - let op_ctez_target = Tezos.transaction txndata_ctez_target 0tez entrypoint_ctez_target in - - ([op_ctez_target], {storage with drift = drift ; last_drift_update = Tezos.now ; target = target}) + let op_ctez_target = Tezos.transaction (target, minted) 0tez entrypoint_ctez_target in + ([op_mint_ctez, op_ctez_target], {storage with drift = drift ; last_update = Tezos.now ; target = target; fee_index = new_fee_index}) let main (p, s : parameter * storage) : result = + (* start by updating the fee index *) + let s = update_liquidity_fee_index s in match p with | Withdraw w -> (withdraw s w : result) | Register_deposit r -> (register_deposit s r : result) | Create d -> (create s d : result) | Liquidate l -> (liquidate s l : result) | Mint_or_burn xs -> (mint_or_burn s xs : result) - | Cfmm_price (x,y) -> (cfmm_price s x y : result) + | Cfmm_info ((x,y),z) -> (cfmm_info s x y z : result) | Set_addresses xs -> (set_addresses s xs : result) | Get_target t -> (get_target s t : result) diff --git a/ctez_initial_storage.ml b/ctez_initial_storage.ml new file mode 100644 index 00000000..eb17714c --- /dev/null +++ b/ctez_initial_storage.ml @@ -0,0 +1,7 @@ +{ + ovens = (Big_map.empty : (oven_handle, oven) big_map) ; + target = Bitwise.shift_left 1n 48n ; drift = 0 ; + last_drift_update = ("DEPLOYMENT_DATET00:00:00Z" : timestamp) ; + ctez_fa12_address = ("tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" : address) ; + cfmm_address = ("tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" : address) ; +} diff --git a/fa12.ml b/fa12.ml new file mode 100644 index 00000000..f913addf --- /dev/null +++ b/fa12.ml @@ -0,0 +1,166 @@ +type transfer = + [@layout:comb] + { [@annot:from] address_from : address; + [@annot:to] address_to : address; + value : nat } + +type approve = + [@layout:comb] + { spender : address; + value : nat } + +type mintOrBurn = + [@layout:comb] + { quantity : int ; + target : address } + +type allowance_key = + [@layout:comb] + { owner : address; + spender : address } + +type getAllowance = + [@layout:comb] + { request : allowance_key; + callback : nat contract } + +type getBalance = + [@layout:comb] + { owner : address; + callback : nat contract } + +type getTotalSupply = + [@layout:comb] + { request : unit ; + callback : nat contract } + +type tokens = (address, nat) big_map +type allowances = (allowance_key, nat) big_map + +type storage = + [@layout:comb] + { tokens : tokens; + allowances : allowances; + admin : address; + total_supply : nat; + } + +type parameter = + | Transfer of transfer + | Approve of approve + | MintOrBurn of mintOrBurn + | GetAllowance of getAllowance + | GetBalance of getBalance + | GetTotalSupply of getTotalSupply + +type result = operation list * storage + +[@inline] +let maybe (n : nat) : nat option = + if n = 0n + then (None : nat option) + else Some n + +let transfer (param : transfer) (storage : storage) : result = + let allowances = storage.allowances in + let tokens = storage.tokens in + let allowances = + if Tezos.sender = param.address_from + then allowances + else + let allowance_key = { owner = param.address_from ; spender = Tezos.sender } in + let authorized_value = + match Big_map.find_opt allowance_key allowances with + | Some value -> value + | None -> 0n in + let authorized_value = + match is_nat (authorized_value - param.value) with + | None -> (failwith "NotEnoughAllowance" : nat) + | Some authorized_value -> authorized_value in + Big_map.update allowance_key (maybe authorized_value) allowances in + let tokens = + let from_balance = + match Big_map.find_opt param.address_from tokens with + | Some value -> value + | None -> 0n in + let from_balance = + match is_nat (from_balance - param.value) with + | None -> (failwith "NotEnoughBalance" : nat) + | Some from_balance -> from_balance in + Big_map.update param.address_from (maybe from_balance) tokens in + let tokens = + let to_balance = + match Big_map.find_opt param.address_to tokens with + | Some value -> value + | None -> 0n in + let to_balance = to_balance + param.value in + Big_map.update param.address_to (maybe to_balance) tokens in + (([] : operation list), { storage with tokens = tokens; allowances = allowances }) + +let approve (param : approve) (storage : storage) : result = + let allowances = storage.allowances in + let allowance_key = { owner = Tezos.sender ; spender = param.spender } in + let previous_value = + match Big_map.find_opt allowance_key allowances with + | Some value -> value + | None -> 0n in + begin + if previous_value > 0n && param.value > 0n + then (failwith "UnsafeAllowanceChange") + else (); + let allowances = Big_map.update allowance_key (maybe param.value) allowances in + (([] : operation list), { storage with allowances = allowances }) + end + +let mintOrBurn (param : mintOrBurn) (storage : storage) : result = + begin + if Tezos.sender <> storage.admin + then failwith "OnlyAdmin" + else (); + let tokens = storage.tokens in + let old_balance = + match Big_map.find_opt param.target tokens with + | None -> 0n + | Some bal -> bal in + let new_balance = + match is_nat (old_balance + param.quantity) with + | None -> (failwith "Cannot burn more than the target's balance." : nat) + | Some bal -> bal in + let tokens = Big_map.update param.target (maybe new_balance) storage.tokens in + let total_supply = abs (storage.total_supply + param.quantity) in + (([] : operation list), { storage with tokens = tokens ; total_supply = total_supply }) + end + +let getAllowance (param : getAllowance) (storage : storage) : operation list = + let value = + match Big_map.find_opt param.request storage.allowances with + | Some value -> value + | None -> 0n in + [Tezos.transaction value 0mutez param.callback] + +let getBalance (param : getBalance) (storage : storage) : operation list = + let value = + match Big_map.find_opt param.owner storage.tokens with + | Some value -> value + | None -> 0n in + [Tezos.transaction value 0mutez param.callback] + +let getTotalSupply (param : getTotalSupply) (storage : storage) : operation list = + let total = storage.total_supply in + [Tezos.transaction total 0mutez param.callback] + + + +let main (param, storage : parameter * storage) : result = + begin + if Tezos.amount <> 0mutez + then failwith "DontSendTez" + else (); + match param with + | Transfer param -> transfer param storage + | Approve param -> approve param storage + | MintOrBurn param -> mintOrBurn param storage + | GetAllowance param -> (getAllowance param storage, storage) + | GetBalance param -> (getBalance param storage, storage) + | GetTotalSupply param -> (getTotalSupply param storage, storage) + end diff --git a/fa12.mligo b/fa12.mligo index 7c2fa501..b6c57d2e 100644 --- a/fa12.mligo +++ b/fa12.mligo @@ -149,6 +149,19 @@ let getTotalSupply (param : getTotalSupply) (storage : storage) : operation list let total = storage.total_supply in [Tezos.transaction total 0mutez param.callback] + +[@view] getBalanceOption (owner, s : address * storage) : nat option = + Big_map.find_opt param.owner storage.tokens + +[@view] getBalance (owner, s : address * storage) : nat = + Big_map.find_opt param.owner storage.tokens + match Big_map.find_opt param.owner storage.tokens with + | Some value -> value + | None -> 0n + +[@view] getTotalSupply ((),s : unit * storage) : nat = + storage.total_supply + let main (param, storage : parameter * storage) : result = begin if Tezos.amount <> 0mutez diff --git a/fa12_ctez_initial_storage.ml b/fa12_ctez_initial_storage.ml new file mode 100644 index 00000000..6c08e9e8 --- /dev/null +++ b/fa12_ctez_initial_storage.ml @@ -0,0 +1,7 @@ +{ + tokens = ( Big_map.literal [("tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" : address), 1n] : + (address, nat) big_map) ; + allowances = (Big_map.empty : allowances) ; + admin = ("ADMIN_ADDRESS" : address) ; + total_supply = 1n +} diff --git a/fees.md b/fees.md new file mode 100644 index 00000000..4287ca86 --- /dev/null +++ b/fees.md @@ -0,0 +1 @@ +# \ No newline at end of file diff --git a/oven.ml b/oven.ml new file mode 100644 index 00000000..c8193a5d --- /dev/null +++ b/oven.ml @@ -0,0 +1,48 @@ +#include "oven_types.mligo" + +(fun (p , s : oven_parameter * oven_storage) -> ( + (* error codes *) + let error_WITHDRAW_CAN_ONLY_BE_CALLED_FROM_MAIN_CONTRACT = 1001n in + let error_ONLY_OWNER_CAN_DELEGATE = 1002n in + let error_CANNOT_FIND_REGISTER_DEPOSIT_ENTRYPOINT = 1003n in + let error_UNAUTHORIZED_DEPOSITOR = 1004n in + let error_SET_ANY_OFF_FIRST = 1005n in + let error_ONLY_OWNER_CAN_EDIT_DEPOSITORS = 1006n in + (match p with + (* Withdraw form the oven, can only be called from the main contract. *) + | Oven_withdraw x -> + if Tezos.sender <> s.admin then + (failwith error_WITHDRAW_CAN_ONLY_BE_CALLED_FROM_MAIN_CONTRACT : oven_result) + else + ([Tezos.transaction unit x.0 x.1], s) + (* Change delegation *) + | Oven_delegate ko -> + if Tezos.sender <> s.handle.owner then + (failwith error_ONLY_OWNER_CAN_DELEGATE : oven_result) + else ([Tezos.set_delegate ko], s) + (* Make a deposit. If authorized, this will notify the main contract. *) + | Oven_deposit -> + if Tezos.sender = s.handle.owner or ( + match s.depositors with + | Any -> true + | Whitelist depositors -> Set.mem Tezos.sender depositors + ) then + let register = ( + match (Tezos.get_entrypoint_opt "%register_deposit" s.admin : (register_deposit contract) option) with + | None -> (failwith error_CANNOT_FIND_REGISTER_DEPOSIT_ENTRYPOINT : register_deposit contract) + | Some register -> register) in + (([ Tezos.transaction {amount = Tezos.amount ; handle = s.handle} 0mutez register] : operation list), s) + else + (failwith error_UNAUTHORIZED_DEPOSITOR : oven_result) + (* Edit the set of authorized depositors. Insert tz1authorizeAnyoneToDeposit3AC7qy8Qf to authorize anyone. *) + | Oven_edit_depositor edit -> + if Tezos.sender <> s.handle.owner then + (failwith error_ONLY_OWNER_CAN_EDIT_DEPOSITORS : oven_result) + else + let depositors = (match edit with + | Allow_any allow -> if allow then Any else Whitelist (Set.empty : address set) + | Allow_account x -> let (allow, depositor) = x in (match s.depositors with + | Any -> (failwith error_SET_ANY_OFF_FIRST : depositors) + | Whitelist depositors -> Whitelist ( + if allow then Set.add depositor depositors else Set.remove depositor depositors))) in + (([] : operation list), {s with depositors = depositors})))) \ No newline at end of file diff --git a/oven_types.ml b/oven_types.ml new file mode 100644 index 00000000..f69dccc4 --- /dev/null +++ b/oven_types.ml @@ -0,0 +1,31 @@ +#if !OVEN_TYPES +#define OVEN_TYPES + +type edit = + | Allow_any of bool + | Allow_account of bool * address + +type oven_parameter = + | Oven_delegate of (key_hash option) + | [@annot:default] Oven_deposit + | Oven_edit_depositor of edit + | Oven_withdraw of tez * (unit contract) + +type depositors = + | Any + | Whitelist of address set + +type oven_handle = [@layout:comb] {id : nat ; owner : address} +type register_deposit = [@layout:comb] { handle : oven_handle ; amount : tez } + + +type oven_storage = { + admin : address (* vault admin contract *) ; + handle : oven_handle (* owner of the oven *) ; + depositors : depositors (* who can deposit in the oven *) ; + } +type oven_result = (operation list) * oven_storage + + + +#endif \ No newline at end of file From 0bc3b6238f842f54ba13541839ef3f8da0dce75c Mon Sep 17 00:00:00 2001 From: Arthur B Date: Sun, 27 Nov 2022 15:26:24 -0500 Subject: [PATCH 82/92] contracts compile, modernize deprecated instructions --- cfmm_tez_ctez.mligo | 48 +++++------ ctez.mligo | 60 +++++++------- ctez_initial_storage.mligo | 3 +- fa12.ml | 166 ------------------------------------- fa12.mligo | 23 +++-- oven.mligo | 12 +-- 6 files changed, 74 insertions(+), 238 deletions(-) delete mode 100644 fa12.ml diff --git a/cfmm_tez_ctez.mligo b/cfmm_tez_ctez.mligo index b7cda3b2..0106566d 100644 --- a/cfmm_tez_ctez.mligo +++ b/cfmm_tez_ctez.mligo @@ -88,7 +88,7 @@ type storage = { cashPool : nat ; tezPool : nat ; lqtTotal : nat ; - target : ctez_target ; + target : nat ; ctez_address : address ; cashAddress : address ; lqtAddress : address ; @@ -139,8 +139,6 @@ let mutez_to_natural (a: tez) : nat = a / 1mutez [@inline] let natural_to_mutez (a: nat): tez = a * 1mutez -[@inline] -let is_a_nat (i : int) : nat option = Michelson.is_nat i let ceildiv (numerator : nat) (denominator : nat) : nat = abs ((- numerator) / (int denominator)) @@ -232,8 +230,8 @@ let add_liquidity (param : add_liquidity) (storage: storage) : result = minLqtMinted = minLqtMinted ; maxCashDeposited = maxCashDeposited ; deadline = deadline } = param in - let tezDeposited = mutez_to_natural Tezos.amount in - if Tezos.now >= deadline then + let tezDeposited = mutez_to_natural (Tezos.get_amount ()) in + if (Tezos.get_now ()) >= deadline then (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) else (* The contract is initialized, use the existing exchange rate @@ -253,7 +251,7 @@ let add_liquidity (param : add_liquidity) (storage: storage) : result = tezPool = storage.tezPool + tezDeposited} in (* send cash from sender to self *) - let op_cash = cash_transfer storage Tezos.sender Tezos.self_address cash_deposited in + let op_cash = cash_transfer storage (Tezos.get_sender ()) (Tezos.get_self_address ()) cash_deposited in (* mint lqt cash for them *) let op_lqt = mint_or_burn storage owner (int lqt_minted) in ([op_cash; op_lqt], storage) @@ -266,9 +264,9 @@ let remove_liquidity (param : remove_liquidity) (storage : storage) : result = minCashWithdrawn = minCashWithdrawn ; deadline = deadline } = param in - if Tezos.now >= deadline then + if (Tezos.get_now ()) >= deadline then (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) - else if Tezos.amount > 0mutez then + else if (Tezos.get_amount ()) > 0mutez then (failwith error_AMOUNT_MUST_BE_ZERO : result) else begin let tez_withdrawn : nat = (lqtBurned * storage.tezPool) / storage.lqtTotal in @@ -282,19 +280,19 @@ let remove_liquidity (param : remove_liquidity) (storage : storage) : result = (* Proceed to form the operations and update the storage *) else begin (* calculate lqtTotal, convert int to nat *) - let new_lqtTotal = match (is_a_nat ( storage.lqtTotal - lqtBurned)) with + let new_lqtTotal = match (is_nat ( storage.lqtTotal - lqtBurned)) with (* This check should be unecessary, the fa12 logic normally takes care of it *) | None -> (failwith error_CANNOT_BURN_MORE_THAN_THE_TOTAL_AMOUNT_OF_LQT : nat) | Some n -> n in (* Calculate cashPool, convert int to nat *) - let new_cashPool = match is_a_nat (storage.cashPool - cash_withdrawn) with + let new_cashPool = match is_nat (storage.cashPool - cash_withdrawn) with | None -> (failwith error_CASH_POOL_MINUS_CASH_WITHDRAWN_IS_NEGATIVE : nat) | Some n -> n in let new_tezPool = match is_nat (storage.tezPool - tez_withdrawn) with | None -> (failwith error_TEZ_POOL_MINUS_TEZ_WITHDRAWN_IS_NEGATIVE : nat) | Some n -> n in - let op_lqt = mint_or_burn storage Tezos.sender (0 - lqtBurned) in - let op_cash = cash_transfer storage Tezos.self_address Tezos.sender cash_withdrawn in + let op_lqt = mint_or_burn storage (Tezos.get_sender ()) (0 - lqtBurned) in + let op_cash = cash_transfer storage (Tezos.get_self_address ()) (Tezos.get_sender ()) cash_withdrawn in let op_tez = tez_transfer to_ tez_withdrawn in let storage = {storage with tezPool = new_tezPool ; lqtTotal = new_lqtTotal ; cashPool = new_cashPool} in ([op_lqt; op_cash; op_tez], storage) @@ -302,7 +300,7 @@ let remove_liquidity (param : remove_liquidity) (storage : storage) : result = end let ctez_target ((target, minted): ctez_target) (storage : storage) = - if Tezos.sender <> storage.ctez_address then + if (Tezos.get_sender ()) <> storage.ctez_address then (failwith error_CALLER_MUST_BE_CTEZ : result) else let updated_target = target in @@ -315,8 +313,8 @@ let tez_to_cash (param : tez_to_cash) (storage : storage) = minCashBought = minCashBought ; deadline = deadline ; rounds = rounds } = param in - let tezSold = mutez_to_natural Tezos.amount in - if Tezos.now >= deadline then + let tezSold = mutez_to_natural (Tezos.get_amount ()) in + if (Tezos.get_now ()) >= deadline then (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) else begin (* We don't check that tezPool > 0, because that is impossible @@ -339,7 +337,7 @@ let tez_to_cash (param : tez_to_cash) (storage : storage) = let storage = { storage with tezPool = storage.tezPool + tezSold ; cashPool = new_cashPool } in (* Send tez from sender to self. *) (* Send cash_withdrawn from exchange to sender. *) - let op_cash = cash_transfer storage Tezos.self_address to_ cash_bought in + let op_cash = cash_transfer storage (Tezos.get_self_address ()) to_ cash_bought in ([op_cash], storage) end @@ -353,9 +351,9 @@ let cash_to_tez (param : cash_to_tez) (storage : storage) = rounds = rounds } = param in - if Tezos.now >= deadline then + if (Tezos.get_now ()) >= deadline then (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) - else if Tezos.amount > 0mutez then + else if (Tezos.get_amount ()) > 0mutez then (failwith error_AMOUNT_MUST_BE_ZERO : result) else (* We don't check that cashPool > 0, because that is impossible @@ -370,7 +368,7 @@ let cash_to_tez (param : cash_to_tez) (storage : storage) = bought_after_fee in - let op_cash = cash_transfer storage Tezos.sender Tezos.self_address cashSold in + let op_cash = cash_transfer storage (Tezos.get_sender ()) (Tezos.get_self_address ()) cashSold in let op_tez = tez_transfer to_ tez_bought in let new_tezPool = match is_nat (storage.tezPool - tez_bought) with | None -> (failwith error_ASSERTION_VIOLATED_TEZ_BOUGHT_SHOULD_BE_LESS_THAN_TEZPOOL : nat) @@ -383,11 +381,11 @@ let cash_to_tez (param : cash_to_tez) (storage : storage) = let default_ (storage : storage) : result = (* Entrypoint to allow depositing tez. *) (* update tezPool *) - let storage = {storage with tezPool = storage.tezPool + mutez_to_natural Tezos.amount} in (([] : operation list), storage) + let storage = {storage with tezPool = storage.tezPool + mutez_to_natural (Tezos.get_amount ())} in (([] : operation list), storage) let set_lqt_address (lqtAddress : address) (storage : storage) : result = - if Tezos.amount > 0mutez then + if (Tezos.get_amount ()) > 0mutez then (failwith error_AMOUNT_MUST_BE_ZERO : result) else if storage.lqtAddress <> null_address then (failwith error_LQT_ADDRESS_ALREADY_SET : result) @@ -407,10 +405,10 @@ let tez_to_token (param : tez_to_token) (storage : storage) : result = | None -> (failwith error_INVALID_INTERMEDIATE_CONTRACT : cash_to_token contract) | Some c -> c) in - if Tezos.now >= deadline then + if (Tezos.get_now ()) >= deadline then (failwith error_THE_CURRENT_TIME_MUST_BE_LESS_THAN_THE_DEADLINE : result) else - let tezSold = mutez_to_natural Tezos.amount in + let tezSold = mutez_to_natural (Tezos.get_amount ()) in (* We don't check that cashPool > 0, because that is impossible unless all liquidity has been removed. *) let cash_bought = (let bought = trade_dtez_for_dcash storage.tezPool storage.cashPool tezSold storage.target rounds in @@ -440,14 +438,14 @@ let tez_to_token (param : tez_to_token) (storage : storage) : result = let update_consumer (operations, storage : result) : result = - if storage.lastOracleUpdate = Tezos.now + if storage.lastOracleUpdate = (Tezos.get_now ()) then (operations, storage) else let consumer = match (Tezos.get_contract_opt storage.consumerEntrypoint : (((nat * nat) * nat) contract) option) with | None -> (failwith error_CANNOT_GET_CFMM_PRICE_ENTRYPOINT_FROM_CONSUMER : ((nat * nat) * nat) contract) | Some c -> c in ((Tezos.transaction ((marginal_price storage.tezPool storage.cashPool storage.target), storage.cashPool) 0mutez consumer) :: operations, - {storage with lastOracleUpdate = Tezos.now}) + {storage with lastOracleUpdate = (Tezos.get_now ())}) let get_marginal_price (param : get_marginal_price) (storage : storage) : result = let price = marginal_price storage.tezPool storage.cashPool storage.target in diff --git a/ctez.mligo b/ctez.mligo index 87603be8..956493a5 100644 --- a/ctez.mligo +++ b/ctez.mligo @@ -25,7 +25,7 @@ type parameter = | Liquidate of liquidate | Register_deposit of register_deposit | Mint_or_burn of mint_or_burn - | Cfmm_info of nat * nat * nat + | Cfmm_info of (nat * nat) * nat | Set_addresses of set_addresses | Get_target of nat contract @@ -60,6 +60,8 @@ type result = (operation list) * storage [@inline] let error_EXCESSIVE_CTEZ_MINTING = 12n [@inline] let error_CALLER_MUST_BE_CFMM = 13n [@inline] let error_INVALID_CTEZ_TARGET_ENTRYPOINT = 14n +[@inline] let error_IMPOSSIBLE = 999n (* an error that should never happen *) + #include "oven.mligo" @@ -70,9 +72,9 @@ let get_oven (handle : oven_handle) (s : storage) : oven = | None -> (failwith error_OVEN_DOESNT_EXIST : oven) | Some oven -> (* Adjust the amount of outstanding ctez in the oven, record the fee index at that time. *) let ctez_outstanding = abs((- oven.ctez_outstanding * oven.fee_index) / s.fee_index) in - {oven with fee_index = fee_index ; ctez_outstanding = ctez_outstanding} + {oven with fee_index = s.fee_index ; ctez_outstanding = ctez_outstanding} -let is_under_collateralized (oven : oven) (target : nat) (fee_index : nat): bool = +let is_under_collateralized (oven : oven) (target : nat) : bool = (15n * oven.tez_balance) < (Bitwise.shift_right (oven.ctez_outstanding * target) 44n) * 1mutez let get_oven_withdraw (oven_address : address) : (tez * (unit contract)) contract = @@ -92,18 +94,18 @@ let get_ctez_mint_or_burn (fa12_address : address) : (int * address) contract = (* Views *) -[@view] let get_fee_index ((), s: unit * storage) : nat = s.fee_index -[@view] let get_target ((), s : unit * storage) : nat = s.target +[@view] let view_fee_index ((), s: unit * storage) : nat = s.fee_index +[@view] let view_target ((), s : unit * storage) : nat = s.target (* Entrypoint Functions *) let create (s : storage) (create : create) : result = - let handle = { id = create.id ; owner = Tezos.sender } in + let handle = { id = create.id ; owner = Tezos.get_sender () } in if Big_map.mem handle s.ovens then (failwith error_OVEN_ALREADY_EXISTS : result) else let (origination_op, oven_address) : operation * address = - create_oven create.delegate Tezos.amount { admin = Tezos.self_address ; handle = handle ; depositors = create.depositors } in - let oven = {tez_balance = Tezos.amount ; ctez_outstanding = 0n ; address = oven_address ; fee_index = s.fee_index} in + create_oven create.delegate (Tezos.get_amount ()) { admin = Tezos.get_self_address () ; handle = handle ; depositors = create.depositors } in + let oven = {tez_balance = (Tezos.get_amount ()) ; ctez_outstanding = 0n ; address = oven_address ; fee_index = s.fee_index} in let ovens = Big_map.update handle (Some oven) s.ovens in ([origination_op], {s with ovens = ovens}) @@ -116,12 +118,14 @@ let set_addresses (s : storage) (addresses : set_addresses) : result = (([] : operation list), {s with ctez_fa12_address = addresses.ctez_fa12_address ; cfmm_address = addresses.cfmm_address}) let withdraw (s : storage) (p : withdraw) : result = - let handle = {id = p.id ; owner = Tezos.sender} in + let handle = {id = p.id ; owner = Tezos.get_sender ()} in let oven : oven = get_oven handle s in let oven_contract = get_oven_withdraw oven.address in (* Check for undercollateralization *) - let new_balance = oven.tez_balance - p.amount in + let new_balance = match (oven.tez_balance - p.amount) with + | None -> (failwith error_EXCESSIVE_TEZ_WITHDRAWAL : tez) + | Some x -> x in let oven = {oven with tez_balance = new_balance} in let ovens = Big_map.update handle (Some oven) s.ovens in let s = {s with ovens = ovens} in @@ -133,7 +137,7 @@ let withdraw (s : storage) (p : withdraw) : result = let register_deposit (s : storage) (p : register_deposit) : result = (* First check that the call is legit *) let oven = get_oven p.handle s in - if oven.address <> Tezos.sender then + if oven.address <> Tezos.get_sender () then (failwith error_INVALID_CALLER_FOR_OVEN_OWNER : result) else (* register the increased balance *) @@ -145,27 +149,29 @@ let register_deposit (s : storage) (p : register_deposit) : result = let liquidate (s: storage) (p : liquidate) : result = let oven : oven = get_oven p.handle s in if is_under_collateralized oven s.target then - let remaining_ctez = match Michelson.is_nat (oven.ctez_outstanding - p.quantity) with + let remaining_ctez = match is_nat (oven.ctez_outstanding - p.quantity) with | None -> (failwith error_CANNOT_BURN_MORE_THAN_OUTSTANDING_AMOUNT_OF_CTEZ : nat) | Some n -> n in (* get 32/31 of the target price, meaning there is a 1/31 penalty for the oven owner for being liquidated *) let extracted_balance = (Bitwise.shift_right (p.quantity * s.target) 43n) * 1mutez / 31n in (* 43 is 48 - log2(32) *) - let new_balance = oven.tez_balance - extracted_balance in + let new_balance = match oven.tez_balance - extracted_balance with + | None -> (failwith error_IMPOSSIBLE : tez) + | Some x -> x in let oven = {oven with ctez_outstanding = remaining_ctez ; tez_balance = new_balance} in let ovens = Big_map.update p.handle (Some oven) s.ovens in let s = {s with ovens = ovens} in let oven_contract = get_oven_withdraw oven.address in let op_take_collateral = Tezos.transaction (extracted_balance, p.to_) 0mutez oven_contract in let ctez_mint_or_burn = get_ctez_mint_or_burn s.ctez_fa12_address in - let op_burn_ctez = Tezos.transaction (-p.quantity, Tezos.sender) 0mutez ctez_mint_or_burn in + let op_burn_ctez = Tezos.transaction (-p.quantity, Tezos.get_sender ()) 0mutez ctez_mint_or_burn in ([op_burn_ctez ; op_take_collateral], s) else (failwith error_OVEN_NOT_UNDERCOLLATERALIZED : result) let mint_or_burn (s : storage) (p : mint_or_burn) : result = - let handle = { id = p.id ; owner = Tezos.sender } in + let handle = { id = p.id ; owner = Tezos.get_sender () } in let oven : oven = get_oven handle s in - let ctez_outstanding = match Michelson.is_nat (oven.ctez_outstanding + p.quantity) with + let ctez_outstanding = match is_nat (oven.ctez_outstanding + p.quantity) with | None -> (failwith error_CANNOT_BURN_MORE_THAN_OUTSTANDING_AMOUNT_OF_CTEZ : nat) | Some n -> n in let oven = {oven with ctez_outstanding = ctez_outstanding} in @@ -176,18 +182,18 @@ let mint_or_burn (s : storage) (p : mint_or_burn) : result = (* mint or burn quantity in the fa1.2 of ctez *) else let ctez_mint_or_burn = get_ctez_mint_or_burn s.ctez_fa12_address in - ([Tezos.transaction (p.quantity, Tezos.sender) 0mutez ctez_mint_or_burn], s) + ([Tezos.transaction (p.quantity, Tezos.get_sender ()) 0mutez ctez_mint_or_burn], s) let get_target (storage : storage) (callback : nat contract) : result = ([Tezos.transaction storage.target 0mutez callback], storage) let cfmm_info (storage : storage) (price_numerator : nat) (price_denominator : nat) (cash_pool : nat): result = - if Tezos.sender <> storage.cfmm_address then + if Tezos.get_sender () <> storage.cfmm_address then (failwith error_CALLER_MUST_BE_CFMM : result) else (* get the new target *) - let delta = abs (Tezos.now - storage.last_update) in + let delta = abs (Tezos.get_now () - storage.last_update) in let target = storage.target in let d_target = Bitwise.shift_right (target * (abs storage.drift) * delta) 48n in (* We assume that `target - d_target < 0` never happens for economic reasons. @@ -218,18 +224,18 @@ let cfmm_info (storage : storage) (price_numerator : nat) (price_denominator : n (* Compute what the liquidity fee shoud be, based on the ratio of total outstanding ctez to ctez in cfmm *) let outstanding = ( - match (Tezos.call_view "total_supply" () storage.fa12_address) with + match (Tezos.call_view "viewTotalSupply" () storage.ctez_fa12_address) with | None -> (failwith unit : nat) | Some n-> n ) in (* fee_r is given as a multiple of 2^(-48)... note that 2^(-27) Np / s ~ 0.98 cNp / year, so roughly a max of 1% / year *) - let fee_r = if 16n * cashPool < outstanding then 2097152n else if 8n * cashPool > outstanding then 2097152n else - (Bitwise.shift_left (outstanding - 8 * cashPool) 22) / (outstanding) in + let fee_r = if 16n * cash_pool < outstanding then 2097152n else if 8n * cash_pool > outstanding then 2097152n else + (Bitwise.shift_left (abs (outstanding - 8n * cash_pool)) 22n) / (outstanding) in - let new_fee_index = storage.fee_index + Bitwise.shift_right (delta * storage.fee_index * fee_r) 48 in + let new_fee_index = storage.fee_index + Bitwise.shift_right (delta * storage.fee_index * fee_r) 48n in (* Compute how many ctez have implicitly been minted since the last update *) (* We round this down while we round the ctez owed up. This leads, over time, to slightly overestimating the outstanding ctez, which is conservative. *) - let minted = outstanding * (abs (new_fee_index - storage.fee_index)) / storage.fee_index in + let minted = outstanding * (new_fee_index - storage.fee_index) / storage.fee_index in (* Create the operation to explicitly mint the ctez in the FA12 contract, and credit it to the CFMM *) let ctez_mint_or_burn = get_ctez_mint_or_burn storage.ctez_fa12_address in @@ -241,12 +247,10 @@ let cfmm_info (storage : storage) (price_numerator : nat) (price_denominator : n (match (Tezos.get_entrypoint_opt "%ctezTarget" cfmm_address : (nat * nat) contract option) with | None -> (failwith error_INVALID_CTEZ_TARGET_ENTRYPOINT : (nat * nat) contract) | Some c -> c ) in - let op_ctez_target = Tezos.transaction (target, minted) 0tez entrypoint_ctez_target in - ([op_mint_ctez, op_ctez_target], {storage with drift = drift ; last_update = Tezos.now ; target = target; fee_index = new_fee_index}) + let op_ctez_target = Tezos.transaction (target, abs minted) 0tez entrypoint_ctez_target in + ([op_mint_ctez ; op_ctez_target], {storage with drift = drift ; last_update = Tezos.get_now () ; target = target; fee_index = new_fee_index}) let main (p, s : parameter * storage) : result = - (* start by updating the fee index *) - let s = update_liquidity_fee_index s in match p with | Withdraw w -> (withdraw s w : result) | Register_deposit r -> (register_deposit s r : result) diff --git a/ctez_initial_storage.mligo b/ctez_initial_storage.mligo index eb17714c..d2cd5c69 100644 --- a/ctez_initial_storage.mligo +++ b/ctez_initial_storage.mligo @@ -1,7 +1,8 @@ { ovens = (Big_map.empty : (oven_handle, oven) big_map) ; target = Bitwise.shift_left 1n 48n ; drift = 0 ; - last_drift_update = ("DEPLOYMENT_DATET00:00:00Z" : timestamp) ; + last_update = ("DEPLOYMENT_DATET00:00:00Z" : timestamp) ; ctez_fa12_address = ("tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" : address) ; cfmm_address = ("tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" : address) ; + fee_index = 281474976710656n ; (*2^48*) } diff --git a/fa12.ml b/fa12.ml deleted file mode 100644 index f913addf..00000000 --- a/fa12.ml +++ /dev/null @@ -1,166 +0,0 @@ -type transfer = - [@layout:comb] - { [@annot:from] address_from : address; - [@annot:to] address_to : address; - value : nat } - -type approve = - [@layout:comb] - { spender : address; - value : nat } - -type mintOrBurn = - [@layout:comb] - { quantity : int ; - target : address } - -type allowance_key = - [@layout:comb] - { owner : address; - spender : address } - -type getAllowance = - [@layout:comb] - { request : allowance_key; - callback : nat contract } - -type getBalance = - [@layout:comb] - { owner : address; - callback : nat contract } - -type getTotalSupply = - [@layout:comb] - { request : unit ; - callback : nat contract } - -type tokens = (address, nat) big_map -type allowances = (allowance_key, nat) big_map - -type storage = - [@layout:comb] - { tokens : tokens; - allowances : allowances; - admin : address; - total_supply : nat; - } - -type parameter = - | Transfer of transfer - | Approve of approve - | MintOrBurn of mintOrBurn - | GetAllowance of getAllowance - | GetBalance of getBalance - | GetTotalSupply of getTotalSupply - -type result = operation list * storage - -[@inline] -let maybe (n : nat) : nat option = - if n = 0n - then (None : nat option) - else Some n - -let transfer (param : transfer) (storage : storage) : result = - let allowances = storage.allowances in - let tokens = storage.tokens in - let allowances = - if Tezos.sender = param.address_from - then allowances - else - let allowance_key = { owner = param.address_from ; spender = Tezos.sender } in - let authorized_value = - match Big_map.find_opt allowance_key allowances with - | Some value -> value - | None -> 0n in - let authorized_value = - match is_nat (authorized_value - param.value) with - | None -> (failwith "NotEnoughAllowance" : nat) - | Some authorized_value -> authorized_value in - Big_map.update allowance_key (maybe authorized_value) allowances in - let tokens = - let from_balance = - match Big_map.find_opt param.address_from tokens with - | Some value -> value - | None -> 0n in - let from_balance = - match is_nat (from_balance - param.value) with - | None -> (failwith "NotEnoughBalance" : nat) - | Some from_balance -> from_balance in - Big_map.update param.address_from (maybe from_balance) tokens in - let tokens = - let to_balance = - match Big_map.find_opt param.address_to tokens with - | Some value -> value - | None -> 0n in - let to_balance = to_balance + param.value in - Big_map.update param.address_to (maybe to_balance) tokens in - (([] : operation list), { storage with tokens = tokens; allowances = allowances }) - -let approve (param : approve) (storage : storage) : result = - let allowances = storage.allowances in - let allowance_key = { owner = Tezos.sender ; spender = param.spender } in - let previous_value = - match Big_map.find_opt allowance_key allowances with - | Some value -> value - | None -> 0n in - begin - if previous_value > 0n && param.value > 0n - then (failwith "UnsafeAllowanceChange") - else (); - let allowances = Big_map.update allowance_key (maybe param.value) allowances in - (([] : operation list), { storage with allowances = allowances }) - end - -let mintOrBurn (param : mintOrBurn) (storage : storage) : result = - begin - if Tezos.sender <> storage.admin - then failwith "OnlyAdmin" - else (); - let tokens = storage.tokens in - let old_balance = - match Big_map.find_opt param.target tokens with - | None -> 0n - | Some bal -> bal in - let new_balance = - match is_nat (old_balance + param.quantity) with - | None -> (failwith "Cannot burn more than the target's balance." : nat) - | Some bal -> bal in - let tokens = Big_map.update param.target (maybe new_balance) storage.tokens in - let total_supply = abs (storage.total_supply + param.quantity) in - (([] : operation list), { storage with tokens = tokens ; total_supply = total_supply }) - end - -let getAllowance (param : getAllowance) (storage : storage) : operation list = - let value = - match Big_map.find_opt param.request storage.allowances with - | Some value -> value - | None -> 0n in - [Tezos.transaction value 0mutez param.callback] - -let getBalance (param : getBalance) (storage : storage) : operation list = - let value = - match Big_map.find_opt param.owner storage.tokens with - | Some value -> value - | None -> 0n in - [Tezos.transaction value 0mutez param.callback] - -let getTotalSupply (param : getTotalSupply) (storage : storage) : operation list = - let total = storage.total_supply in - [Tezos.transaction total 0mutez param.callback] - - - -let main (param, storage : parameter * storage) : result = - begin - if Tezos.amount <> 0mutez - then failwith "DontSendTez" - else (); - match param with - | Transfer param -> transfer param storage - | Approve param -> approve param storage - | MintOrBurn param -> mintOrBurn param storage - | GetAllowance param -> (getAllowance param storage, storage) - | GetBalance param -> (getBalance param storage, storage) - | GetTotalSupply param -> (getTotalSupply param storage, storage) - end diff --git a/fa12.mligo b/fa12.mligo index b6c57d2e..1aca3ce8 100644 --- a/fa12.mligo +++ b/fa12.mligo @@ -65,10 +65,10 @@ let transfer (param : transfer) (storage : storage) : result = let allowances = storage.allowances in let tokens = storage.tokens in let allowances = - if Tezos.sender = param.address_from + if Tezos.get_sender () = param.address_from then allowances else - let allowance_key = { owner = param.address_from ; spender = Tezos.sender } in + let allowance_key = { owner = param.address_from ; spender = Tezos.get_sender () } in let authorized_value = match Big_map.find_opt allowance_key allowances with | Some value -> value @@ -99,7 +99,7 @@ let transfer (param : transfer) (storage : storage) : result = let approve (param : approve) (storage : storage) : result = let allowances = storage.allowances in - let allowance_key = { owner = Tezos.sender ; spender = param.spender } in + let allowance_key = { owner = Tezos.get_sender (); spender = param.spender } in let previous_value = match Big_map.find_opt allowance_key allowances with | Some value -> value @@ -114,7 +114,7 @@ let approve (param : approve) (storage : storage) : result = let mintOrBurn (param : mintOrBurn) (storage : storage) : result = begin - if Tezos.sender <> storage.admin + if Tezos.get_sender () <> storage.admin then failwith "OnlyAdmin" else (); let tokens = storage.tokens in @@ -150,21 +150,20 @@ let getTotalSupply (param : getTotalSupply) (storage : storage) : operation list [Tezos.transaction total 0mutez param.callback] -[@view] getBalanceOption (owner, s : address * storage) : nat option = - Big_map.find_opt param.owner storage.tokens +[@view] let viewBalanceOption (owner, s : address * storage) : nat option = + Big_map.find_opt owner s.tokens -[@view] getBalance (owner, s : address * storage) : nat = - Big_map.find_opt param.owner storage.tokens - match Big_map.find_opt param.owner storage.tokens with +[@view] let viewBalance (owner, s : address * storage) : nat = + match Big_map.find_opt owner s.tokens with | Some value -> value | None -> 0n -[@view] getTotalSupply ((),s : unit * storage) : nat = - storage.total_supply +[@view] let viewTotalSupply ((),s : unit * storage) : nat = + s.total_supply let main (param, storage : parameter * storage) : result = begin - if Tezos.amount <> 0mutez + if Tezos.get_amount () <> 0mutez then failwith "DontSendTez" else (); match param with diff --git a/oven.mligo b/oven.mligo index 142c9d63..b5cc3557 100644 --- a/oven.mligo +++ b/oven.mligo @@ -13,32 +13,32 @@ let create_oven (delegate : key_hash option) (amnt : tez) (storage : oven_storag (match p with (* Withdraw form the oven, can only be called from the main contract. *) | Oven_withdraw x -> - if Tezos.sender <> s.admin then + if Tezos.get_sender () <> s.admin then (failwith error_WITHDRAW_CAN_ONLY_BE_CALLED_FROM_MAIN_CONTRACT : oven_result) else ([Tezos.transaction unit x.0 x.1], s) (* Change delegation *) | Oven_delegate ko -> - if Tezos.sender <> s.handle.owner then + if Tezos.get_sender () <> s.handle.owner then (failwith error_ONLY_OWNER_CAN_DELEGATE : oven_result) else ([Tezos.set_delegate ko], s) (* Make a deposit. If authorized, this will notify the main contract. *) | Oven_deposit -> - if Tezos.sender = s.handle.owner or ( + if Tezos.get_sender () = s.handle.owner or ( match s.depositors with | Any -> true - | Whitelist depositors -> Set.mem Tezos.sender depositors + | Whitelist depositors -> Set.mem (Tezos.get_sender ()) depositors ) then let register = ( match (Tezos.get_entrypoint_opt "%register_deposit" s.admin : (register_deposit contract) option) with | None -> (failwith error_CANNOT_FIND_REGISTER_DEPOSIT_ENTRYPOINT : register_deposit contract) | Some register -> register) in - (([ Tezos.transaction {amount = Tezos.amount ; handle = s.handle} 0mutez register] : operation list), s) + (([ Tezos.transaction {amount = Tezos.get_amount () ; handle = s.handle} 0mutez register] : operation list), s) else (failwith error_UNAUTHORIZED_DEPOSITOR : oven_result) (* Edit the set of authorized depositors. Insert tz1authorizeAnyoneToDeposit3AC7qy8Qf to authorize anyone. *) | Oven_edit_depositor edit -> - if Tezos.sender <> s.handle.owner then + if Tezos.get_sender () <> s.handle.owner then (failwith error_ONLY_OWNER_CAN_EDIT_DEPOSITORS : oven_result) else let depositors = (match edit with From 1a2114bf68e42cff2a1ac64eb3e7cfaad9cd0958 Mon Sep 17 00:00:00 2001 From: Arthur B Date: Fri, 3 Feb 2023 19:57:23 -0500 Subject: [PATCH 83/92] fix bug in fee_r computation --- ctez.mligo | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ctez.mligo b/ctez.mligo index 956493a5..c3d7e29c 100644 --- a/ctez.mligo +++ b/ctez.mligo @@ -71,7 +71,7 @@ let get_oven (handle : oven_handle) (s : storage) : oven = match Big_map.find_opt handle s.ovens with | None -> (failwith error_OVEN_DOESNT_EXIST : oven) | Some oven -> (* Adjust the amount of outstanding ctez in the oven, record the fee index at that time. *) - let ctez_outstanding = abs((- oven.ctez_outstanding * oven.fee_index) / s.fee_index) in + let ctez_outstanding = abs((- oven.ctez_outstanding * s.fee_index) / oven.fee_index) in {oven with fee_index = s.fee_index ; ctez_outstanding = ctez_outstanding} let is_under_collateralized (oven : oven) (target : nat) : bool = @@ -228,9 +228,9 @@ let cfmm_info (storage : storage) (price_numerator : nat) (price_denominator : n | None -> (failwith unit : nat) | Some n-> n ) in - (* fee_r is given as a multiple of 2^(-48)... note that 2^(-27) Np / s ~ 0.98 cNp / year, so roughly a max of 1% / year *) - let fee_r = if 16n * cash_pool < outstanding then 2097152n else if 8n * cash_pool > outstanding then 2097152n else - (Bitwise.shift_left (abs (outstanding - 8n * cash_pool)) 22n) / (outstanding) in + (* fee_r is given as a multiple of 2^(-48)... note that 2^(-32) Np / s ~ 0.73 cNp / year, so roughly a max of 0.73% / year *) + let fee_r = if 16n * cash_pool < outstanding then 65536n else if 8n * cash_pool > outstanding then 0n else + (Bitwise.shift_left (abs (outstanding - 8n * cash_pool)) 17n) / (outstanding) in let new_fee_index = storage.fee_index + Bitwise.shift_right (delta * storage.fee_index * fee_r) 48n in (* Compute how many ctez have implicitly been minted since the last update *) From 921c0338422a03df82e1c09d3fa6bc7a88e75683 Mon Sep 17 00:00:00 2001 From: Arthur Breitman Date: Sat, 30 Dec 2023 10:17:52 +0000 Subject: [PATCH 84/92] add a few todos --- TODO | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 TODO diff --git a/TODO b/TODO new file mode 100644 index 00000000..964d056d --- /dev/null +++ b/TODO @@ -0,0 +1,4 @@ +- check for bugs +- ensure last price of block is used or perhaps VWAP +- negative fees when restoring AMM towards target +- ensure drift is updated based on the price before the last update, not the last update From e31e748221bb61ae55dc3bf27801c30f1bfb041c Mon Sep 17 00:00:00 2001 From: Arthur Breitman Date: Sat, 30 Dec 2023 11:28:07 +0000 Subject: [PATCH 85/92] add todo line --- TODO | 1 + 1 file changed, 1 insertion(+) diff --git a/TODO b/TODO index 964d056d..9474c35b 100644 --- a/TODO +++ b/TODO @@ -2,3 +2,4 @@ - ensure last price of block is used or perhaps VWAP - negative fees when restoring AMM towards target - ensure drift is updated based on the price before the last update, not the last update +- lock LPs for a few blocks after they initiate withdrawal request From 404e7c6ba61a4493fd23e5224f2a6282cc1c1165 Mon Sep 17 00:00:00 2001 From: Arthur Breitman Date: Wed, 28 Feb 2024 16:26:16 +0000 Subject: [PATCH 86/92] limit dex --- limit_dex.mligo | 190 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 limit_dex.mligo diff --git a/limit_dex.mligo b/limit_dex.mligo new file mode 100644 index 00000000..969d0d82 --- /dev/null +++ b/limit_dex.mligo @@ -0,0 +1,190 @@ +type add_tez_liquidity = +[@layout:comb] +{ + owner : address ; (* address that will own the liqudity *) + minLiquidity : nat ; (* minimum amount of liquidity to add *) + deadline : timestamp ; (* deadline for the transaction *) +} + +type add_ctez_liquidity = +[@layout:comb] +{ + owner : address ; (* address that will own the liqudity *) + minLiquidity : nat ; (* minimum amount of liquidity to add *) + deadline : timestamp ; (* deadline for the transaction *) + ctezDeposited : nat ; (* amount of ctez to deposit *) +} + +type remove_tez_liquidity = +[@layout:comb] +{ + [@annot:to] to_: address ; (* address to receive to *) + lpt : nat ; (* amount of liquidity to remove *) + minTezReceived : nat ; (* minimum amount of tez to receive *) + minCtezReceived : nat ; (* minimum amount of ctez to receive *) + mintSubsidyReceived : nat ; (* minimum amount of ctez subsidy to receive *) + deadline : timestamp ; (* deadline for the transaction *) +} + +type remove_ctez_liquidity = +[@layout:comb] +{ + [@annot:to] to_: address ; (* address to receive to *) + lpt : nat ; (* amount of liquidity to remove *) + minTezReceived : nat ; (* minimum amount of tez to receive *) + minCtezReceived : nat ; (* minimum amount of ctez to receive *) + mintSubsidyReceived : nat ; (* minimum amount of ctez subsidy to receive *) + deadline : timestamp ; (* deadline for the transaction *) +} + +type tez_to_ctez = +[@layout:comb] +{ + [@annot:to] to_: address ; (* address that will own the ctez *) + deadline : timestamp ; (* deadline for the transaction *) + minCtezBought : nat ; (* minimum amount of ctez to buy *) +} + +type ctez_to_tez = +[@layout:comb] +{ + [@annot:to] to_: address ; (* address that will own the tez *) + deadline : timestamp ; (* deadline for the transaction *) + minTezBought : nat ; (* minimum amount of tez to buy *) +} + +type withdraw_for_tez_liquidity = +[@layout:comb] +{ + [@annot:to] to_: address ; (* address to withdraw to *) +} + +type withdraw_for_ctez_liquidity = +[@layout:comb] +{ + [@annot:to] to_: address ; (* address to withdraw to, note that here you receive both ctez and tez + because ctez is received as part of the subsidy *) +} + +type liquidity_owner = +[@layout:comb] +{ + lpt : nat ; (* LP token amount *) + owed : nat ; (* amount of the proceeds token owed to the contract *) + subsidy_owed : nat ; (* amount of ctez subsidy owed to the contract *) +} + + +type half_dex = +[@layout:comb] +{ + liquidity_owners : (address, liquidity_owner) big_map ; (* map of liquidity owners *) + total_lpt : nat ; (* total amount of liquidity tokens *) + total_liquidity : nat ; (* total amount of liquidity *) + total_proceeds : nat ; (* total amount accumulated from proceeds *) + total_subsidy : nat ; (* total amount accumulated from subsidy *) +} + +type storage = +[@layout:comb] +{ + sell_ctez : half_dex ; + sell_tez : half_dex ; + target : nat ; (* target / 2^48 is the target price of ctez in tez *) (* todo, logic for update *) + Q : nat ; (* Q is the desired quantity of ctez in the ctez half dex, + floor(Q * target) is the desired quantity of tez in the tez half dex *) +} + +[@inline] +let ceildiv (numerator : nat) (denominator : nat) : nat = abs ((- numerator) / (int denominator)) + + +[@entry] +let add_ctez_liquidity (param : add_ctez_liquidity) (s : storage) : storage * operation list = + let d_lpt = (param.ctezDeposited * s.sell_ctez.total_lpt) / s.sell_ctez.total_liquidity in + if d_lpt < param.minLiquidity then + failwith "transaction would create insufficient liquidity" + else if Tezos.get_now () > param.deadline then + failwith "deadline has passed" + else + // lpt is going to be lpt + d_lpt + // ctez is going to be ctez + d_ctez + // if the owner already has liquidity, we need to update the owed amount + // otherwise we need to create a new liquidity owner + let liquidity_owner = match Big_map.find_opt param.owner s.sell_ctez.liquidity_owners with + | None -> {owner : param.owner ; lpt : 0n ; owed : 0n ; subsidy_owed : 0n} + | Some lo -> lo in + + let d_tez = ceildiv (sell_ctez.total_accumulated_sales * d_lpt) sell_ctez.total_lpt in + let d_subsidy_owed = ceildiv (sell_ctez.total_accumulated_subsidy * d_lpt) sell_ctez.total_lpt in + + let liquidity_owner = { liquidity_owner with + lpt = liquidity_owner.lpt + d_lpt ; + owed = liquidity_owner.owed + d_tez ; + subsidy_owed = liquidityowner.subsidy_owed + d_subsidy_owed } in + + let liquidity_owners = Big_map.update param.owner (Some liquidity_owner) liquidity_owners in + + let sell_ctez = {s.sell_ctez with + liquidity_owners = liquidity_owners ; + total_lpt = s.sell_ctez.total_lpt + d_lpt ; + total_liquidity = s.sell_ctez.total_liquidity + param.ctezDeposited ; + } in + + let receive_ctez = Tezos.transaction (param.owner, (s.liquidity_dex_address, params.ctezDeposited)) 0mutez s.ctez_token_contract in + ({s with sell_ctez = half_dex}, [receive_ctez]) + +[@entry] +let remove_ctez_liquidity (param : remove_ctez_liquidity) (s : storage) : storage * operation list = + if Tezos.get_now () > param.deadline then + failwith "deadline has passed" + else + let ctez_removed = (param.lpt * s.sell_ctez.total_liquidity) / s.sell_ctez.total_lpt in + let tez_removed = (param.lpt * s.sell_ctez.total_accumulated_sales) / s.sell_ctez.total_lpt in + let subsidy_removed = (param.lpt * s.sell_ctez.total_accumulated_subsidy) / s.sell_ctez.total_lpt in + let liquidity_owner = match Big_map.find_opt param.owner s.sell_ctez.liquidity_owners with + | None -> failwith "no liquidity owner" + | Some lo -> lo in + if liquidity_owner.lpt < param.lpt then + failwith "insufficient liquidity" + else if ctez_removed < param.minCtezReceived then + failwith "insufficient ctez would be received" + else + (* compute the amount of tez to receive after netting the owed amount *) + let tez_to_receive = tez_removed - liquidity_owner.owed in + if tez_to_receive < param.minTezReceived then + failwith "insufficient tez would be received" + else + let (owed, tez_to_receive) = + if tez_to_receive < 0n then + (abs (liquidity_owner.owed - tez_removed), 0n) + else + (0n, abs tez_to_receive) + in + let subsidy_to_receive = subsidy_removed - liquidity_owner.subsidy_owed in + if subsidy_to_receive < param.minSubsidyReceived then + failwith "insufficient subsidy would be received" + else + let (subsidy_owed, subsidy_to_receive) = + if subsidy_to_receive < 0n then + (abs (liquidity_owner.subsidy_owed - subsidy_removed), 0n) + else + (0n, abs subsidy_to_receive) + in + let liquidity_ower = { liquidity_owner with + lpt = abs (liquidity_owner.lpt - param.lpt) ; + owed = owed ; + subsidy_owed = subsidy_owed } in + let liquidity_owners = Big_map.update param.owner (Some liquidity_owner) s.sell_ctez.liquidity_owners in + let sell_ctez = {s.sell_ctez with + liquidity_owners = liquidity_owners ; + total_lpt = abs (s.sell_ctez.total_lpt - param.lpt) ; + total_liquidity = abs (s.sell_ctez.total_liquidity - ctez_removed) ; + total_accumulated_sales = s.sell_ctez.total_accumulated_sales - tez_removed ; + total_accumulated_subsidy = s.sell_ctez.total_accumulated_subsidy - subsidy_removed ; + } in + let receive_ctez = Tezos.transaction (param.to_, (s.ctez_token_contract, ctez_removed)) 0mutez s.liquidity_dex_address in + let receive_subsidy = Tezos.transaction (param.to_, (s.ctez_token_contract, subsidy_to_receive)) 0mutez s.liquidity_dex_address in + let receive_tez = Tezos.transaction () (tez_to_receive * 1mutez) param.to_ in + ({s with sell_ctez = sell_ctez}, [receive_ctez; receive_subsidy; receive_tez]) + From febdfe98f4e64fa6c707e08e847aa9491c1ffb6e Mon Sep 17 00:00:00 2001 From: Alistair O'Brien Date: Wed, 28 Feb 2024 16:56:26 +0000 Subject: [PATCH 87/92] limit dex --- limit_dex.mligo | 171 +++++++++++++++++++++++++----------------------- 1 file changed, 88 insertions(+), 83 deletions(-) diff --git a/limit_dex.mligo b/limit_dex.mligo index 969d0d82..ffe7340d 100644 --- a/limit_dex.mligo +++ b/limit_dex.mligo @@ -22,7 +22,7 @@ type remove_tez_liquidity = lpt : nat ; (* amount of liquidity to remove *) minTezReceived : nat ; (* minimum amount of tez to receive *) minCtezReceived : nat ; (* minimum amount of ctez to receive *) - mintSubsidyReceived : nat ; (* minimum amount of ctez subsidy to receive *) + minSubsidyReceived : nat ; (* minimum amount of ctez subsidy to receive *) deadline : timestamp ; (* deadline for the transaction *) } @@ -33,7 +33,7 @@ type remove_ctez_liquidity = lpt : nat ; (* amount of liquidity to remove *) minTezReceived : nat ; (* minimum amount of tez to receive *) minCtezReceived : nat ; (* minimum amount of ctez to receive *) - mintSubsidyReceived : nat ; (* minimum amount of ctez subsidy to receive *) + minSubsidyReceived : nat ; (* minimum amount of ctez subsidy to receive *) deadline : timestamp ; (* deadline for the transaction *) } @@ -91,100 +91,105 @@ type storage = sell_ctez : half_dex ; sell_tez : half_dex ; target : nat ; (* target / 2^48 is the target price of ctez in tez *) (* todo, logic for update *) - Q : nat ; (* Q is the desired quantity of ctez in the ctez half dex, + q : nat ; (* Q is the desired quantity of ctez in the ctez half dex, floor(Q * target) is the desired quantity of tez in the tez half dex *) + liquidity_dex_address : address ; (* address of the liquidity dex *) + ctez_token_contract : address ; (* address of the ctez token contract *) } [@inline] let ceildiv (numerator : nat) (denominator : nat) : nat = abs ((- numerator) / (int denominator)) +[@inline] +let redeem_amount (x : nat) (reserve : nat) (total : nat) : nat = + // The redeem rate is defined as + // RX_i(t_0, t_1) := r_i / total(t_0, t_1) + // The redeem amount is defined as + // v = x / RX_i(t_0, t_1) = (x * total(t_0, t_1)) / reserve + (x * total) / reserve + + [@entry] let add_ctez_liquidity (param : add_ctez_liquidity) (s : storage) : storage * operation list = - let d_lpt = (param.ctezDeposited * s.sell_ctez.total_lpt) / s.sell_ctez.total_liquidity in - if d_lpt < param.minLiquidity then - failwith "transaction would create insufficient liquidity" - else if Tezos.get_now () > param.deadline then - failwith "deadline has passed" - else - // lpt is going to be lpt + d_lpt - // ctez is going to be ctez + d_ctez - // if the owner already has liquidity, we need to update the owed amount - // otherwise we need to create a new liquidity owner - let liquidity_owner = match Big_map.find_opt param.owner s.sell_ctez.liquidity_owners with - | None -> {owner : param.owner ; lpt : 0n ; owed : 0n ; subsidy_owed : 0n} - | Some lo -> lo in - - let d_tez = ceildiv (sell_ctez.total_accumulated_sales * d_lpt) sell_ctez.total_lpt in - let d_subsidy_owed = ceildiv (sell_ctez.total_accumulated_subsidy * d_lpt) sell_ctez.total_lpt in - - let liquidity_owner = { liquidity_owner with - lpt = liquidity_owner.lpt + d_lpt ; - owed = liquidity_owner.owed + d_tez ; - subsidy_owed = liquidityowner.subsidy_owed + d_subsidy_owed } in - - let liquidity_owners = Big_map.update param.owner (Some liquidity_owner) liquidity_owners in - - let sell_ctez = {s.sell_ctez with - liquidity_owners = liquidity_owners ; - total_lpt = s.sell_ctez.total_lpt + d_lpt ; - total_liquidity = s.sell_ctez.total_liquidity + param.ctezDeposited ; - } in - - let receive_ctez = Tezos.transaction (param.owner, (s.liquidity_dex_address, params.ctezDeposited)) 0mutez s.ctez_token_contract in - ({s with sell_ctez = half_dex}, [receive_ctez]) + let d_lpt = redeem_amount param.ctezDeposited s.sell_ctez.total_liquidity s.sell_ctez.total_lpt in + let () = assert_with_error (d_lpt >= param.minLiquidity) "transaction would create insufficient liquidity" in + let () = assert_with_error (Tezos.get_now () <= param.deadline) "deadline has passed" in + // lpt is going to be lpt + d_lpt + // ctez is going to be ctez + d_ctez + // if the owner already has liquidity, we need to update the owed amount + // otherwise we need to create a new liquidity owner + let liquidity_owner = + Option.value + { lpt = 0n ; owed = 0n ; subsidy_owed = 0n} + (Big_map.find_opt param.owner s.sell_ctez.liquidity_owners) + in + let d_tez = ceildiv (s.sell_ctez.total_proceeds * d_lpt) s.sell_ctez.total_lpt in + let d_subsidy_owed = ceildiv (s.sell_ctez.total_subsidy * d_lpt) s.sell_ctez.total_lpt in + // Update liquidity owner + let liquidity_owner = { liquidity_owner with + lpt = liquidity_owner.lpt + d_lpt ; + owed = liquidity_owner.owed + d_tez ; + subsidy_owed = liquidity_owner.subsidy_owed + d_subsidy_owed } in + let liquidity_owners = Big_map.update param.owner (Some liquidity_owner) s.sell_ctez.liquidity_owners in + + let sell_ctez = {s.sell_ctez with + liquidity_owners = liquidity_owners ; + total_lpt = s.sell_ctez.total_lpt + d_lpt ; + total_liquidity = s.sell_ctez.total_liquidity + param.ctezDeposited ; + } in + + let receive_ctez = Tezos.transaction (param.owner, (s.liquidity_dex_address, params.ctezDeposited)) 0mutez s.ctez_token_contract in + ({s with sell_ctez = half_dex}, [receive_ctez]) [@entry] let remove_ctez_liquidity (param : remove_ctez_liquidity) (s : storage) : storage * operation list = - if Tezos.get_now () > param.deadline then - failwith "deadline has passed" - else - let ctez_removed = (param.lpt * s.sell_ctez.total_liquidity) / s.sell_ctez.total_lpt in - let tez_removed = (param.lpt * s.sell_ctez.total_accumulated_sales) / s.sell_ctez.total_lpt in - let subsidy_removed = (param.lpt * s.sell_ctez.total_accumulated_subsidy) / s.sell_ctez.total_lpt in - let liquidity_owner = match Big_map.find_opt param.owner s.sell_ctez.liquidity_owners with - | None -> failwith "no liquidity owner" - | Some lo -> lo in - if liquidity_owner.lpt < param.lpt then - failwith "insufficient liquidity" - else if ctez_removed < param.minCtezReceived then - failwith "insufficient ctez would be received" - else - (* compute the amount of tez to receive after netting the owed amount *) - let tez_to_receive = tez_removed - liquidity_owner.owed in - if tez_to_receive < param.minTezReceived then - failwith "insufficient tez would be received" - else - let (owed, tez_to_receive) = - if tez_to_receive < 0n then - (abs (liquidity_owner.owed - tez_removed), 0n) - else - (0n, abs tez_to_receive) - in - let subsidy_to_receive = subsidy_removed - liquidity_owner.subsidy_owed in - if subsidy_to_receive < param.minSubsidyReceived then - failwith "insufficient subsidy would be received" - else - let (subsidy_owed, subsidy_to_receive) = - if subsidy_to_receive < 0n then - (abs (liquidity_owner.subsidy_owed - subsidy_removed), 0n) - else - (0n, abs subsidy_to_receive) - in - let liquidity_ower = { liquidity_owner with + let () = assert_with_error (Tezos.get_now () <= param.deadline) "deadline has passed" in + let ctez_removed = (param.lpt * s.sell_ctez.total_liquidity) / s.sell_ctez.total_lpt in + let tez_removed = (param.lpt * s.sell_ctez.total_proceeds) / s.sell_ctez.total_lpt in + let subsidy_removed = (param.lpt * s.sell_ctez.total_subsidy) / s.sell_ctez.total_lpt in + let owner = Tezos.get_sender () in + let liquidity_owner = Option.unopt_with_error (Big_map.find_opt owner s.sell_ctez.liquidity_owners) "no liquidity owner" in + let () = assert_with_error (liquidity_owner.lpt >= param.lpt) "insufficient liquidity" in + let () = assert_with_error (ctez_removed >= param.minCtezReceived) "insufficient ctez would be received" in + + (* compute the amount of tez to receive after netting the owed amount *) + let tez_to_receive = tez_removed - liquidity_owner.owed in + let () = assert_with_error (tez_to_receive >= int param.minTezReceived) "insufficient tez would be received" in + let (owed, tez_to_receive) = + if tez_to_receive < 0 then + (abs (liquidity_owner.owed - tez_removed), 0n) + else + (0n, abs tez_to_receive) + in + (* computed the amount of subsidy to recieve after netting the owed subsidy amount *) + + + let subsidy_to_receive = subsidy_removed - liquidity_owner.subsidy_owed in + let () = assert_with_error (subsidy_to_receive >= int param.minSubsidyReceived) "insufficient subsidy would be received" in + let (subsidy_owed, subsidy_to_receive) = + if subsidy_to_receive < 0 then + (abs (liquidity_owner.subsidy_owed - subsidy_removed), 0n) + else + (0n, abs subsidy_to_receive) + in + + let liquidity_ower = { liquidity_owner with lpt = abs (liquidity_owner.lpt - param.lpt) ; owed = owed ; subsidy_owed = subsidy_owed } in - let liquidity_owners = Big_map.update param.owner (Some liquidity_owner) s.sell_ctez.liquidity_owners in - let sell_ctez = {s.sell_ctez with - liquidity_owners = liquidity_owners ; - total_lpt = abs (s.sell_ctez.total_lpt - param.lpt) ; - total_liquidity = abs (s.sell_ctez.total_liquidity - ctez_removed) ; - total_accumulated_sales = s.sell_ctez.total_accumulated_sales - tez_removed ; - total_accumulated_subsidy = s.sell_ctez.total_accumulated_subsidy - subsidy_removed ; - } in - let receive_ctez = Tezos.transaction (param.to_, (s.ctez_token_contract, ctez_removed)) 0mutez s.liquidity_dex_address in - let receive_subsidy = Tezos.transaction (param.to_, (s.ctez_token_contract, subsidy_to_receive)) 0mutez s.liquidity_dex_address in - let receive_tez = Tezos.transaction () (tez_to_receive * 1mutez) param.to_ in - ({s with sell_ctez = sell_ctez}, [receive_ctez; receive_subsidy; receive_tez]) + let liquidity_owners = Big_map.update owner (Some liquidity_owner) s.sell_ctez.liquidity_owners in + + let sell_ctez = {s.sell_ctez with + liquidity_owners = liquidity_owners ; + total_lpt = abs (s.sell_ctez.total_lpt - param.lpt) ; + total_liquidity = abs (s.sell_ctez.total_liquidity - ctez_removed) ; + total_proceeds = abs (s.sell_ctez.total_proceeds - tez_removed) ; + total_subsidy = abs (s.sell_ctez.total_subsidy - subsidy_removed) ; + } in + let receive_ctez = Tezos.transaction (param.to_, (s.ctez_token_contract, ctez_removed)) 0mutez s.liquidity_dex_address in + let receive_subsidy = Tezos.transaction (param.to_, (s.ctez_token_contract, subsidy_to_receive)) 0mutez s.liquidity_dex_address in + let receive_tez = Tezos.transaction () (tez_to_receive * 1mutez) param.to_ in + ({s with sell_ctez = sell_ctez}, [receive_ctez; receive_subsidy; receive_tez]) + From 8a04dd5d95de0306eb029fc970e5e6102c31428a Mon Sep 17 00:00:00 2001 From: Arthur Breitman Date: Wed, 28 Feb 2024 18:12:32 +0000 Subject: [PATCH 88/92] add some code for newton descent --- limit_dex.mligo | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/limit_dex.mligo b/limit_dex.mligo index ffe7340d..ad8c696b 100644 --- a/limit_dex.mligo +++ b/limit_dex.mligo @@ -41,8 +41,10 @@ type tez_to_ctez = [@layout:comb] { [@annot:to] to_: address ; (* address that will own the ctez *) + refund : address ; (* address to refund extra tez to *) deadline : timestamp ; (* deadline for the transaction *) - minCtezBought : nat ; (* minimum amount of ctez to buy *) + ctezBought : nat ; (* amount of ctez to buy *) + maxTezSold : tez ; (* maximum amount of tez to sell *) } type ctez_to_tez = @@ -51,6 +53,7 @@ type ctez_to_tez = [@annot:to] to_: address ; (* address that will own the tez *) deadline : timestamp ; (* deadline for the transaction *) minTezBought : nat ; (* minimum amount of tez to buy *) + ctezSold : nat ; (* amount of ctez to sell *) } type withdraw_for_tez_liquidity = @@ -193,3 +196,46 @@ let remove_ctez_liquidity (param : remove_ctez_liquidity) (s : storage) : storag ({s with sell_ctez = sell_ctez}, [receive_ctez; receive_subsidy; receive_tez]) +let newton_step (q : int) (t : int) (_Q : int) (dq : int): int = + (* + (3 dq⁴ + 6 dq² (q - Q)² + 8 dq³ (-q + Q) + 80 Q³ t) / (4 ((dq - q)³ + 3 (dq - q)² Q + 3 (dq - q) Q² + 21 Q³)) + todo, check that implementation below is correct + *) + let q_m_Q = q - _Q in + let dq_m_q = dq - q in + let dq_m_q_sq = dq_m_q * dq_m_q in + let dq_m_q_cu = dq_m_q_sq * dq_m_q in + let _Q_sq = _Q * _Q in + let _Q_cu = _Q_sq * _Q in + + let num = 3 * dq * dq * dq * dq + 6 * dq * dq * q_m_Q * q_m_Q + 8 * dq * dq * dq * (-q_m_Q) + 80 * _Q_cu * t in + let denom = 4 * (dq_m_q_cu + 3 * dq_m_q_sq * _Q + 3 * dq_m_q * _Q_sq + 21 * _Q_cu) in + + num / denom + +let invert (q : int) (t : int) (_Q : int) : int = + (* note that the price is generally very nearly linear, after all the worth marginal price is 1.05, so Newton + converges stupidly fast *) + let dq = newton_step q t _Q t in + let dq = newton_step q t _Q dq in + let dq = newton_step q t _Q dq in + dq + + + +[@entry] +let tez_to_ctez (param : tez_to_ctez) (s : storage) : storage * operation list = + let () = assert_with_error (Tezos.get_now () <= param.deadline) "deadline has passed" in + let tez_ + (* The amount of tez that will be bought is calculated by integrating a polynomial which is a function of the fraction u purchased over q + * the polynomial, representing the marginal price is given as (21 - 3 * u + 3 u^2 - u^3) / 20 + * again, u is the quantity of ctez purchased over q which represents this characteristic quantity of ctez in the ctez half dex.&& + * the integral of this polynomial between u = 0 and u = x / q (where x will be ctez_to_sell) is is given as + * (21 * u - 3 * u^2 / 2 + u^3 - u^4 / 4) / 20 + * or (cts(cts(cts^2-3q^2)+42 q^3))/(40q^4) *) + let cts = ctez_to_sell in let q = s.q in + let q2 = q * q in + let d_tez = (cts * (cts * (cts * cts - 3 * q2) + 42 * q * q2)) / (40 * q2 * q2) in + + + \ No newline at end of file From 33bd648f010b49a093c8c651532c736d502e7c9d Mon Sep 17 00:00:00 2001 From: Arthur Breitman Date: Thu, 29 Feb 2024 14:59:25 +0000 Subject: [PATCH 89/92] add a note about how to compute withdraw --- limit_dex.mligo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/limit_dex.mligo b/limit_dex.mligo index ad8c696b..adc16e28 100644 --- a/limit_dex.mligo +++ b/limit_dex.mligo @@ -238,4 +238,4 @@ let tez_to_ctez (param : tez_to_ctez) (s : storage) : storage * operation list = let d_tez = (cts * (cts * (cts * cts - 3 * q2) + 42 * q * q2)) / (40 * q2 * q2) in - \ No newline at end of file +(* withdraw: you can withdraw x so long as x + owed < lpt * total_proceeds / total_lpt, after which owed := owed + x *) From 9986fc144a2956f2e7b7ee90d4a799de29257967 Mon Sep 17 00:00:00 2001 From: Alistair O'Brien Date: Tue, 26 Mar 2024 16:13:44 +0000 Subject: [PATCH 90/92] wip --- limit_dex.mligo | 144 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 122 insertions(+), 22 deletions(-) diff --git a/limit_dex.mligo b/limit_dex.mligo index adc16e28..1cb12de2 100644 --- a/limit_dex.mligo +++ b/limit_dex.mligo @@ -41,10 +41,8 @@ type tez_to_ctez = [@layout:comb] { [@annot:to] to_: address ; (* address that will own the ctez *) - refund : address ; (* address to refund extra tez to *) deadline : timestamp ; (* deadline for the transaction *) - ctezBought : nat ; (* amount of ctez to buy *) - maxTezSold : tez ; (* maximum amount of tez to sell *) + minCtezBought : nat ; (* minimum amount of ctez to buy *) } type ctez_to_tez = @@ -62,7 +60,7 @@ type withdraw_for_tez_liquidity = [@annot:to] to_: address ; (* address to withdraw to *) } -type withdraw_for_ctez_liquidity = +type withdraw_for_ctez_half_dex = [@layout:comb] { [@annot:to] to_: address ; (* address to withdraw to, note that here you receive both ctez and tez @@ -88,18 +86,42 @@ type half_dex = total_subsidy : nat ; (* total amount accumulated from subsidy *) } + +type fa12_transfer = + [@layout:comb] + { [@annot:from] address_from : address; + [@annot:to] address_to : address; + value : nat } + type storage = [@layout:comb] { sell_ctez : half_dex ; sell_tez : half_dex ; target : nat ; (* target / 2^48 is the target price of ctez in tez *) (* todo, logic for update *) - q : nat ; (* Q is the desired quantity of ctez in the ctez half dex, + _Q : nat ; (* Q is the desired quantity of ctez in the ctez half dex, floor(Q * target) is the desired quantity of tez in the tez half dex *) - liquidity_dex_address : address ; (* address of the liquidity dex *) - ctez_token_contract : address ; (* address of the ctez token contract *) + liquidity_dex_address : address ; (* address of the liquidity dex *) + ctez_token_contract : address ; (* address of the ctez token contract *) + ctez_contract : address ; (* address of the ctez contract *) + last_update : nat; } +// retrieve _Q and target + + + +let update_ctez_contract_if_needed (s : storage) : operation list * storage = + let curr_level = Tezos.get_level () in + if s.last_update <> curr_level then + let ctez_contract = (Tezos.get_entrypoint "%dex_update" s.ctez_contract : (nat * nat) contract) in + let operation = Tezos.transaction (s.sell_ctez.total_liquidity, s.sell_tez.total_liquidity) 0mutez ctez_contract in + let target , _Q = Option.value_with_error "dex_info entrypoint must exist" (Tezos.call_view "%dex_info" () s.ctez_contract) in + ([operation], {s with last_update = curr_level; target = target; _Q = _Q}) + else + ([], s) + + [@inline] let ceildiv (numerator : nat) (denominator : nat) : nat = abs ((- numerator) / (int denominator)) @@ -142,7 +164,7 @@ let add_ctez_liquidity (param : add_ctez_liquidity) (s : storage) : storage * op total_liquidity = s.sell_ctez.total_liquidity + param.ctezDeposited ; } in - let receive_ctez = Tezos.transaction (param.owner, (s.liquidity_dex_address, params.ctezDeposited)) 0mutez s.ctez_token_contract in + let receive_ctez = Tezos.transaction (param.owner, (s.liquidity_dex_address, param.ctezDeposited)) 0mutez s.ctez_token_contract in ({s with sell_ctez = half_dex}, [receive_ctez]) [@entry] @@ -196,12 +218,25 @@ let remove_ctez_liquidity (param : remove_ctez_liquidity) (s : storage) : storag ({s with sell_ctez = sell_ctez}, [receive_ctez; receive_subsidy; receive_tez]) -let newton_step (q : int) (t : int) (_Q : int) (dq : int): int = +let min (x : nat) (y : nat) : nat = if x < y then x else y + +let clamp_nat (x : int) : nat = + match is_nat x with + | None -> 0n + | Some x -> x + +let newton_step (q : nat) (t : nat) (_Q : nat) (dq : nat): int = (* (3 dq⁴ + 6 dq² (q - Q)² + 8 dq³ (-q + Q) + 80 Q³ t) / (4 ((dq - q)³ + 3 (dq - q)² Q + 3 (dq - q) Q² + 21 Q³)) todo, check that implementation below is correct + TODO: optimize the computation of [q - _Q] and other constants + (A dq^2 +B)/(C + dq(D+dq(4dq-E))) *) + // ensures that dq < q + let dq = min dq q in + // assert q < _Q (due to clamp at [invert]) let q_m_Q = q - _Q in + let dq_m_q = dq - q in let dq_m_q_sq = dq_m_q * dq_m_q in let dq_m_q_cu = dq_m_q_sq * dq_m_q in @@ -213,29 +248,94 @@ let newton_step (q : int) (t : int) (_Q : int) (dq : int): int = num / denom -let invert (q : int) (t : int) (_Q : int) : int = +let invert (q : nat) (t : nat) (_Q : nat) : nat = + (* q is the current amount, + t is the amount you want to trade + _Q is the target amount + *) (* note that the price is generally very nearly linear, after all the worth marginal price is 1.05, so Newton - converges stupidly fast *) - let dq = newton_step q t _Q t in - let dq = newton_step q t _Q dq in - let dq = newton_step q t _Q dq in - dq + converges stupidly fast *) + let q = min q _Q in + let dq = clamp_nat (newton_step q t _Q t) in + let dq = clamp_nat (newton_step q t _Q dq) in + let dq = clamp_nat (newton_step q t _Q dq) in + let result = dq - dq / 1_000_000_000 - 1 in + match is_nat result with + | None -> failwith "trade size too small" + | Some x -> x +let append t1 t2 = List.fold_right (fun (x, tl) -> x :: tl) t1 t2 [@entry] -let tez_to_ctez (param : tez_to_ctez) (s : storage) : storage * operation list = +let tez_to_ctez (param : tez_to_ctez) (s : storage) : operation list * storage = + let update_ops, s = update_ctez_contract_if_needed s in + let () = assert_with_error (Tezos.get_now () <= param.deadline) "deadline has passed" in - let tez_ (* The amount of tez that will be bought is calculated by integrating a polynomial which is a function of the fraction u purchased over q * the polynomial, representing the marginal price is given as (21 - 3 * u + 3 u^2 - u^3) / 20 * again, u is the quantity of ctez purchased over q which represents this characteristic quantity of ctez in the ctez half dex.&& * the integral of this polynomial between u = 0 and u = x / q (where x will be ctez_to_sell) is is given as * (21 * u - 3 * u^2 / 2 + u^3 - u^4 / 4) / 20 * or (cts(cts(cts^2-3q^2)+42 q^3))/(40q^4) *) - let cts = ctez_to_sell in let q = s.q in - let q2 = q * q in - let d_tez = (cts * (cts * (cts * cts - 3 * q2) + 42 * q * q2)) / (40 * q2 * q2) in +// let cts = ctez_to_sell in let q = s.q in +// let q2 = q * q in +// let d_tez = (cts * (cts * (cts * cts - 3 * q2) + 42 * q * q2)) / (40 * q2 * q2) in + + let t = Bitwise.shift_left (Tezos.get_amount () / 1mutez) 48n / s.target in + let ctez_to_sell = invert s.sell_ctez.total_liquidity t s._Q in + let () = assert_with_error (ctez_to_sell >= param.minCtezBought) "insufficient ctez would be bought" in + let () = assert_with_error (ctez_to_sell <= s.sell_ctez.total_liquidity) "insufficient ctez in the dex" in + // Update dex + let half_dex = s.sell_ctez in + let half_dex: half_dex = { half_dex with total_liquidity = clamp_nat (half_dex.total_liquidity - ctez_to_sell); total_proceeds = half_dex.total_proceeds + (Tezos.get_amount () / 1mutez) } in + // Transfer ctez to the buyer + let fa_contract = (Tezos.get_entrypoint "%transfer" s.ctez_token_contract : fa12_transfer contract) in + let receive_ctez = Tezos.transaction { address_from = s.liquidity_dex_address; address_to = param.to_; value = ctez_to_sell } 0mutez fa_contract in + // Deal with subsidy later + (append update_ops [receive_ctez], {s with sell_ctez = half_dex}) + + +let implicit_transfer (to_ : address) (amt : tez) : operation = + let contract = (Tezos.get_entrypoint "%default" to_ : unit contract) in + Tezos.transaction () amt contract + +let ctez_transfer (s : storage) (to_ : address) (value: nat) : operation = + let fa_contract = (Tezos.get_entrypoint "%transfer" s.ctez_token_contract : fa12_transfer contract) in + let receive_ctez = Tezos.transaction { address_from = s.liquidity_dex_address; address_to = to_; value } 0mutez fa_contract in + receive_ctez + + +[@entry] +let withdraw_for_ctez_half_dex (param : withdraw_for_ctez_half_dex) (s: storage) : operation list * storage = + // withdraw: you can withdraw x so long as x + owed < lpt * total_proceeds / total_lpt, after which owed := owed + x + // So, my thoughts on withdrawing: + // you can withdraw x, so long as x + owe < lpt * total_proceeds / total_lpt + // owe := owe + x + // 2:57 PM + // owe never decreases, it's basically a tally of everything you've ever withdrawn + // 2:57 PM + // so when you add liquidity, it's like you added all those proceeds, and then withdrew lpt * total_proceeds / total_lpt + // + // TL;DR: proceeds = tez + total owed; proceeds doesn't increase - -(* withdraw: you can withdraw x so long as x + owed < lpt * total_proceeds / total_lpt, after which owed := owed + x *) + let owner = Tezos.get_sender () in + let half_dex = s.sell_ctez in + let liquidity_owner = Option.value_with_error "no liquidity owner" (Big_map.find_opt owner half_dex.liquidity_owners) in + let share_of_proceeds = liquidity_owner.lpt * half_dex.total_proceeds / half_dex.total_lpt in + // proceeds in tez + let amount_proceeds_withdrawn = clamp_nat (share_of_proceeds - liquidity_owner.owed) in + let share_of_subsidy = liquidity_owner.lpt * half_dex.total_subsidy / half_dex.total_lpt in + // subsidy in ctez + let amount_subsidy_withdrawn = clamp_nat (share_of_subsidy - liquidity_owner.subsidy_owed) in + // liquidity owner owes the full share of proceeds + let liquidity_owner = { liquidity_owner with owed = share_of_proceeds; subsidy_owed = share_of_subsidy } in + // update half dex + let half_dex = { half_dex with liquidity_owners = Big_map.update owner (Some liquidity_owner) half_dex.liquidity_owners } in + // do transfers + let receive_proceeds = implicit_transfer param.to_ (amount_proceeds_withdrawn * 1mutez) in + let receive_subsidy = ctez_transfer s param.to_ amount_subsidy_withdrawn in + ([receive_proceeds; receive_subsidy], {s with sell_ctez = half_dex}) + + + From eb5d062d6c63e7edf169a1fd35ec3119157be12d Mon Sep 17 00:00:00 2001 From: Alistair O'Brien Date: Wed, 1 May 2024 17:07:20 +0100 Subject: [PATCH 91/92] WIP merging of contracts --- ctez_2.mligo | 617 +++++++++++++++++++++++++++++++++++++++++++++++ limit_dex.mligo | 162 +++++++++++++ oven.mligo | 6 +- oven_types.mligo | 2 +- 4 files changed, 783 insertions(+), 4 deletions(-) create mode 100644 ctez_2.mligo diff --git a/ctez_2.mligo b/ctez_2.mligo new file mode 100644 index 00000000..07797586 --- /dev/null +++ b/ctez_2.mligo @@ -0,0 +1,617 @@ +(* + Order of deployment + 1. Deploy the oven management contract (this contract) + 2. Deploy the fa12 address for the ctez contract, setting the oven management address as admin + 3. Deploy the CFMM, hard coding the oven management contract address as consumer + 4. Deploy the FA12 for the LQT specifying the CFMM as admin + 5. Manually set the LQT FA12 address in the CFMM + 6. Manually set the ctez fa12 address and the cfmm address in the oven management contract +*) + +#include "oven_types.mligo" + + + + +(* End of oven types *) + + +type liquidate = [@layout:comb] { handle : oven_handle ; quantity : nat ; [@annot:to] to_ : unit contract } +type mint_or_burn = [@layout:comb] {id : nat ; quantity : int} + +type parameter = + | Create of create + | Withdraw of withdraw + | Liquidate of liquidate + | Register_oven_deposit of register_oven_deposit + | Mint_or_burn of mint_or_burn + | Cfmm_info of (nat * nat) * nat + | Set_addresses of set_addresses + | Get_target of nat contract + +type oven = {tez_balance : tez ; ctez_outstanding : nat ; address : address ; fee_index : nat} + + +type liquidity_owner = +{ + lpt : nat ; (* LP token amount *) + owed : nat ; (* amount of the proceeds token owed to the contract *) + subsidy_owed : nat ; (* amount of ctez subsidy owed to the contract *) +} + +type half_dex = +{ + liquidity_owners : (address, liquidity_owner) big_map ; (* map of liquidity owners *) + total_lpt : nat ; (* total amount of liquidity tokens *) + total_liquidity : nat ; (* total amount of liquidity *) + total_proceeds : nat ; (* total amount accumulated from proceeds *) + total_subsidy : nat ; (* total amount accumulated from subsidy *) + fee_index : nat ; +} + +type storage = { + ovens : (oven_handle, oven) big_map ; + target : nat ; + drift : int ; + last_update : timestamp ; + ctez_fa12_address : address ; (* address of the fa12 contract managing the ctez token *) + sell_ctez : half_dex ; + sell_tez : half_dex ; + _Q : nat ; (* Q is the desired quantity of ctez in the ctez half dex, + floor(Q * target) is the desired quantity of tez in the tez half dex *) +} + +type result = (operation list) * storage + +(* Errors *) + +[@inline] let error_OVEN_ALREADY_EXISTS = 0n +[@inline] let error_INVALID_CALLER_FOR_OVEN_OWNER = 1n +[@inline] let error_CTEZ_FA12_ADDRESS_ALREADY_SET = 2n +[@inline] let error_CFMM_ADDRESS_ALREADY_SET = 3n +[@inline] let error_OVEN_DOESNT_EXIST= 4n +[@inline] let error_OVEN_MISSING_WITHDRAW_ENTRYPOINT = 5n +[@inline] let error_OVEN_MISSING_DEPOSIT_ENTRYPOINT = 6n +[@inline] let error_OVEN_MISSING_DELEGATE_ENTRYPOINT = 7n +[@inline] let error_EXCESSIVE_TEZ_WITHDRAWAL = 8n +[@inline] let error_CTEZ_FA12_CONTRACT_MISSING_MINT_OR_BURN_ENTRYPOINT = 9n +[@inline] let error_CANNOT_BURN_MORE_THAN_OUTSTANDING_AMOUNT_OF_CTEZ = 10n +[@inline] let error_OVEN_NOT_UNDERCOLLATERALIZED = 11n +[@inline] let error_EXCESSIVE_CTEZ_MINTING = 12n +[@inline] let error_CALLER_MUST_BE_CFMM = 13n +[@inline] let error_INVALID_CTEZ_TARGET_ENTRYPOINT = 14n +[@inline] let error_IMPOSSIBLE = 999n (* an error that should never happen *) + + +#include "oven.mligo" + +(* Functions *) + +let get_oven (handle : oven_handle) (s : storage) : oven = + match Big_map.find_opt handle s.ovens with + | None -> (failwith error_OVEN_DOESNT_EXIST : oven) + | Some oven -> + (* Adjust the amount of outstanding ctez in the oven, record the fee index at that time. *) + let new_fee_index = s.sell_ctez.fee_index * s.sell_tez.fee_index in + let ctez_outstanding = abs((- oven.ctez_outstanding * new_fee_index) / oven.fee_index) in + {oven with fee_index = new_fee_index ; ctez_outstanding = ctez_outstanding} + +let is_under_collateralized (oven : oven) (target : nat) : bool = + (15n * oven.tez_balance) < (Bitwise.shift_right (oven.ctez_outstanding * target) 44n) * 1mutez + +let get_oven_withdraw (oven_address : address) : (tez * (unit contract)) contract = + match (Tezos.get_entrypoint_opt "%oven_withdraw" oven_address : (tez * (unit contract)) contract option) with + | None -> (failwith error_OVEN_MISSING_WITHDRAW_ENTRYPOINT : (tez * (unit contract)) contract) + | Some c -> c + +let get_oven_delegate (oven_address : address) : (key_hash option) contract = + match (Tezos.get_entrypoint_opt "%oven_delegate" oven_address : (key_hash option) contract option) with + | None -> (failwith error_OVEN_MISSING_DELEGATE_ENTRYPOINT : (key_hash option) contract) + | Some c -> c + +let get_ctez_mint_or_burn (fa12_address : address) : (int * address) contract = + match (Tezos.get_entrypoint_opt "%mintOrBurn" fa12_address : ((int * address) contract) option) with + | None -> (failwith error_CTEZ_FA12_CONTRACT_MISSING_MINT_OR_BURN_ENTRYPOINT : (int * address) contract) + | Some c -> c + + +(* Views *) +[@view] let view_target ((), s : unit * storage) : nat = s.target + +type create_oven = {id : nat ; delegate : key_hash option ; depositors : depositors } + +(* Entrypoint Functions *) +[@entry] +let create_oven ({ id; delegate; depositors }: create_oven) (s : storage) : result = + let handle = { id ; owner = Tezos.get_sender () } in + if Big_map.mem handle s.ovens then + (failwith error_OVEN_ALREADY_EXISTS : result) + else + let (origination_op, oven_address) : operation * address = + originate_oven delegate (Tezos.get_amount ()) { admin = Tezos.get_self_address () ; handle = handle ; depositors = depositors } in + let oven = {tez_balance = (Tezos.get_amount ()) ; ctez_outstanding = 0n ; address = oven_address ; fee_index = s.sell_ctez.fee_index * s.sell_tez.fee_index} in + let ovens = Big_map.update handle (Some oven) s.ovens in + ([origination_op], {s with ovens = ovens}) + +// called on initialization to set the ctez_fa12_address +[@entry] +let set_ctez_fa12_address (ctez_fa12_address : address) (s : storage) : result = + if s.ctez_fa12_address <> ("tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" : address) then + (failwith error_CTEZ_FA12_ADDRESS_ALREADY_SET : result) + else + (([] : operation list), {s with ctez_fa12_address = ctez_fa12_address}) + +type withdraw = { id : nat ; amount : tez ; [@annot:to] to_ : unit contract } + +[@entry] +let withdraw_from_oven (p : withdraw) (s : storage) : result = + let handle = {id = p.id ; owner = Tezos.get_sender ()} in + let oven : oven = get_oven handle s in + let oven_contract = get_oven_withdraw oven.address in + + (* Check for undercollateralization *) + let new_balance = match (oven.tez_balance - p.amount) with + | None -> (failwith error_EXCESSIVE_TEZ_WITHDRAWAL : tez) + | Some x -> x in + let oven = {oven with tez_balance = new_balance} in + let ovens = Big_map.update handle (Some oven) s.ovens in + let s = {s with ovens = ovens} in + if is_under_collateralized oven s.target then + (failwith error_EXCESSIVE_TEZ_WITHDRAWAL : result) + else + ([Tezos.transaction (p.amount, p.to_) 0mutez oven_contract], s) + + +[@entry] +let register_oven_deposit (p : register_oven_deposit) (s : storage) : result = + (* First check that the call is legit *) + let oven = get_oven p.handle s in + if oven.address <> Tezos.get_sender () then + (failwith error_INVALID_CALLER_FOR_OVEN_OWNER : result) + else + (* register the increased balance *) + let oven = {oven with tez_balance = oven.tez_balance + p.amount} in + let ovens = Big_map.update p.handle (Some oven) s.ovens in + (([] : operation list), {s with ovens = ovens}) + +(* liquidate the oven by burning "quantity" ctez *) +[@entry] +let liquidate_oven (p : liquidate) (s: storage) : result = + let oven : oven = get_oven p.handle s in + if is_under_collateralized oven s.target then + let remaining_ctez = match is_nat (oven.ctez_outstanding - p.quantity) with + | None -> (failwith error_CANNOT_BURN_MORE_THAN_OUTSTANDING_AMOUNT_OF_CTEZ : nat) + | Some n -> n in + (* get 32/31 of the target price, meaning there is a 1/31 penalty for the oven owner for being liquidated *) + let extracted_balance = (Bitwise.shift_right (p.quantity * s.target) 43n) * 1mutez / 31n in (* 43 is 48 - log2(32) *) + let new_balance = match oven.tez_balance - extracted_balance with + | None -> (failwith error_IMPOSSIBLE : tez) + | Some x -> x in + let oven = {oven with ctez_outstanding = remaining_ctez ; tez_balance = new_balance} in + let ovens = Big_map.update p.handle (Some oven) s.ovens in + let s = {s with ovens = ovens} in + let oven_contract = get_oven_withdraw oven.address in + let op_take_collateral = Tezos.transaction (extracted_balance, p.to_) 0mutez oven_contract in + let ctez_mint_or_burn = get_ctez_mint_or_burn s.ctez_fa12_address in + let op_burn_ctez = Tezos.transaction (-p.quantity, Tezos.get_sender ()) 0mutez ctez_mint_or_burn in + ([op_burn_ctez ; op_take_collateral], s) + else + (failwith error_OVEN_NOT_UNDERCOLLATERALIZED : result) + +[@entry] +let mint_or_burn (p : mint_or_burn) (s : storage) : result = + let handle = { id = p.id ; owner = Tezos.get_sender () } in + let oven : oven = get_oven handle s in + let ctez_outstanding = match is_nat (oven.ctez_outstanding + p.quantity) with + | None -> (failwith error_CANNOT_BURN_MORE_THAN_OUTSTANDING_AMOUNT_OF_CTEZ : nat) + | Some n -> n in + let oven = {oven with ctez_outstanding = ctez_outstanding} in + let ovens = Big_map.update handle (Some oven) s.ovens in + let s = {s with ovens = ovens} in + if is_under_collateralized oven s.target then + (failwith error_EXCESSIVE_CTEZ_MINTING : result) + (* mint or burn quantity in the fa1.2 of ctez *) + else + let ctez_mint_or_burn = get_ctez_mint_or_burn s.ctez_fa12_address in + ([Tezos.transaction (p.quantity, Tezos.get_sender ()) 0mutez ctez_mint_or_burn], s) + +[@view] +let get_target () (storage : storage) : nat = storage.target + +[@view] +let get_drift () (storage : storage) : int = storage.drift + +let min (a : nat) b = if a < b then a else b + +[@inline] +let drift_adjustment (storage : storage) : int = + let target = storage.target in + let qc = min storage.sell_ctez.total_liquidity storage._Q in + let qt = min storage.sell_tez.total_liquidity (storage._Q * target) in + let tqc_m_qt = target * qc - qt in + let tQ = target * storage._Q in + tqc_m_qt * tqc_m_qt * tqc_m_qt / (tQ * tQ * tQ) + + +let fee_rate (_Q : nat) (q : nat) : nat = + if 16n * q < _Q then 65536n else if 16n * q > 15n * _Q then 0n else + abs (15 - 14 * q / _Q) * 65536n / 14n + + +let clamp_nat (x : int) : nat = + match is_nat x with + | None -> 0n + | Some x -> x + +let update_fee_index (ctez_fa12_address: address) (delta: nat) (outstanding : nat) (_Q : nat) (dex : half_dex) : half_dex * nat * operation = + let rate = fee_rate _Q dex.total_liquidity in + (* rate is given as a multiple of 2^(-48)... note that 2^(-32) Np / s ~ 0.73 cNp / year, so roughly a max of 0.73% / year *) + let new_fee_index = dex.fee_index + Bitwise.shift_right (delta * dex.fee_index * rate) 48n in + (* Compute how many ctez have implicitly been minted since the last update *) + (* We round this down while we round the ctez owed up. This leads, over time, to slightly overestimating the outstanding ctez, which is conservative. *) + let minted = outstanding * (new_fee_index - dex.fee_index) / dex.fee_index in + + (* Create the operation to explicitly mint the ctez in the FA12 contract, and credit it to the CFMM *) + let ctez_mint_or_burn = get_ctez_mint_or_burn ctez_fa12_address in + let op_mint_ctez = Tezos.transaction (minted, Tezos.get_self_address ()) 0mutez ctez_mint_or_burn in + + {dex with fee_index = new_fee_index; total_subsidy = clamp_nat (dex.total_subsidy + minted) }, clamp_nat (outstanding + minted), op_mint_ctez + + +let housekeeping () (storage : storage) : result = + let curr_timestamp = Tezos.get_now () in + if storage.last_update <> curr_timestamp then + let d_drift = drift_adjustment storage in + (* This is not homegeneous, but setting the constant delta is multiplied with + to 1.0 magically happens to be reasonable. Why? + Because (24 * 3600 / 2^48) * 365.25*24*3600 ~ 0.97%. + This means that the annualized drift changes by roughly one percentage point per day at most. + *) + let new_drift = storage.drift + d_drift in + + let delta = abs (curr_timestamp - storage.last_update) in + let target = storage.target in + let d_target = Bitwise.shift_right (target * (abs storage.drift) * delta) 48n in + (* We assume that `target - d_target < 0` never happens for economic reasons. + Concretely, even drift were as low as -50% annualized, it would take not + updating the target for 1.4 years for a negative number to occur *) + let new_target = if storage.drift < 0 then abs (target - d_target) else target + d_target in + (* Compute what the liquidity fee shoud be, based on the ratio of total outstanding ctez to ctez in dexes *) + let outstanding = ( + match (Tezos.call_view "viewTotalSupply" () storage.ctez_fa12_address) with + | None -> (failwith unit : nat) + | Some n-> n + ) in + let storage = { storage with _Q = outstanding / 20n } in + let sell_ctez, outstanding, op_mint_ctez1 = update_fee_index storage.ctez_fa12_address delta outstanding storage._Q storage.sell_ctez in + let sell_tez, _outstanding, op_mint_ctez2 = update_fee_index storage.ctez_fa12_address delta outstanding (storage._Q * storage.target) storage.sell_tez in + let storage = { storage with sell_ctez = sell_ctez ; sell_tez = sell_tez } in + + ([op_mint_ctez1 ; op_mint_ctez2], {storage with drift = new_drift ; last_update = curr_timestamp ; target = new_target }) + else + ([], storage) + + + +type add_tez_liquidity = +[@layout:comb] +{ + owner : address ; (* address that will own the liqudity *) + minLiquidity : nat ; (* minimum amount of liquidity to add *) + deadline : timestamp ; (* deadline for the transaction *) +} + +type add_ctez_liquidity = +[@layout:comb] +{ + owner : address ; (* address that will own the liqudity *) + minLiquidity : nat ; (* minimum amount of liquidity to add *) + deadline : timestamp ; (* deadline for the transaction *) + ctezDeposited : nat ; (* amount of ctez to deposit *) +} + +type remove_tez_liquidity = +[@layout:comb] +{ + [@annot:to] to_: address ; (* address to receive to *) + lpt : nat ; (* amount of liquidity to remove *) + minTezReceived : nat ; (* minimum amount of tez to receive *) + minCtezReceived : nat ; (* minimum amount of ctez to receive *) + minSubsidyReceived : nat ; (* minimum amount of ctez subsidy to receive *) + deadline : timestamp ; (* deadline for the transaction *) +} + +type remove_ctez_liquidity = +[@layout:comb] +{ + [@annot:to] to_: address ; (* address to receive to *) + lpt : nat ; (* amount of liquidity to remove *) + minTezReceived : nat ; (* minimum amount of tez to receive *) + minCtezReceived : nat ; (* minimum amount of ctez to receive *) + minSubsidyReceived : nat ; (* minimum amount of ctez subsidy to receive *) + deadline : timestamp ; (* deadline for the transaction *) +} + +type tez_to_ctez = +[@layout:comb] +{ + [@annot:to] to_: address ; (* address that will own the ctez *) + deadline : timestamp ; (* deadline for the transaction *) + minCtezBought : nat ; (* minimum amount of ctez to buy *) +} + +type ctez_to_tez = +[@layout:comb] +{ + [@annot:to] to_: address ; (* address that will own the tez *) + deadline : timestamp ; (* deadline for the transaction *) + minTezBought : nat ; (* minimum amount of tez to buy *) + ctezSold : nat ; (* amount of ctez to sell *) +} + +type withdraw_for_tez_liquidity = +[@layout:comb] +{ + [@annot:to] to_: address ; (* address to withdraw to *) +} + +type withdraw_for_ctez_half_dex = +[@layout:comb] +{ + [@annot:to] to_: address ; (* address to withdraw to, note that here you receive both ctez and tez + because ctez is received as part of the subsidy *) +} + + +type fa12_transfer = + [@layout:comb] + { [@annot:from] address_from : address; + [@annot:to] address_to : address; + value : nat } + + +// retrieve _Q and target + + + +let update_ctez_contract_if_needed (s : storage) : operation list * storage = + let curr_level = Tezos.get_level () in + if s.last_update <> curr_level then + let ctez_contract = (Tezos.get_entrypoint "%dex_update" s.ctez_contract : (nat * nat) contract) in + let operation = Tezos.transaction (s.sell_ctez.total_liquidity, s.sell_tez.total_liquidity) 0mutez ctez_contract in + let target , _Q = Option.value_with_error "dex_info entrypoint must exist" (Tezos.call_view "%dex_info" () s.ctez_contract) in + ([operation], {s with last_update = curr_level; target = target; _Q = _Q}) + else + ([], s) + + +[@inline] +let ceildiv (numerator : nat) (denominator : nat) : nat = abs ((- numerator) / (int denominator)) + + +[@inline] +let redeem_amount (x : nat) (reserve : nat) (total : nat) : nat = + // The redeem rate is defined as + // RX_i(t_0, t_1) := r_i / total(t_0, t_1) + // The redeem amount is defined as + // v = x / RX_i(t_0, t_1) = (x * total(t_0, t_1)) / reserve + (x * total) / reserve + + +[@entry] +let add_ctez_liquidity (param : add_ctez_liquidity) (s : storage) : storage * operation list = + let d_lpt = redeem_amount param.ctezDeposited s.sell_ctez.total_liquidity s.sell_ctez.total_lpt in + let () = assert_with_error (d_lpt >= param.minLiquidity) "transaction would create insufficient liquidity" in + let () = assert_with_error (Tezos.get_now () <= param.deadline) "deadline has passed" in + // lpt is going to be lpt + d_lpt + // ctez is going to be ctez + d_ctez + // if the owner already has liquidity, we need to update the owed amount + // otherwise we need to create a new liquidity owner + let liquidity_owner = + Option.value + { lpt = 0n ; owed = 0n ; subsidy_owed = 0n} + (Big_map.find_opt param.owner s.sell_ctez.liquidity_owners) + in + let d_tez = ceildiv (s.sell_ctez.total_proceeds * d_lpt) s.sell_ctez.total_lpt in + let d_subsidy_owed = ceildiv (s.sell_ctez.total_subsidy * d_lpt) s.sell_ctez.total_lpt in + // Update liquidity owner + let liquidity_owner = { liquidity_owner with + lpt = liquidity_owner.lpt + d_lpt ; + owed = liquidity_owner.owed + d_tez ; + subsidy_owed = liquidity_owner.subsidy_owed + d_subsidy_owed } in + let liquidity_owners = Big_map.update param.owner (Some liquidity_owner) s.sell_ctez.liquidity_owners in + + let sell_ctez = {s.sell_ctez with + liquidity_owners = liquidity_owners ; + total_lpt = s.sell_ctez.total_lpt + d_lpt ; + total_liquidity = s.sell_ctez.total_liquidity + param.ctezDeposited ; + } in + + let receive_ctez = Tezos.transaction (param.owner, (s.liquidity_dex_address, param.ctezDeposited)) 0mutez s.ctez_token_contract in + ({s with sell_ctez = half_dex}, [receive_ctez]) + +[@entry] +let remove_ctez_liquidity (param : remove_ctez_liquidity) (s : storage) : storage * operation list = + let () = assert_with_error (Tezos.get_now () <= param.deadline) "deadline has passed" in + let ctez_removed = (param.lpt * s.sell_ctez.total_liquidity) / s.sell_ctez.total_lpt in + let tez_removed = (param.lpt * s.sell_ctez.total_proceeds) / s.sell_ctez.total_lpt in + let subsidy_removed = (param.lpt * s.sell_ctez.total_subsidy) / s.sell_ctez.total_lpt in + let owner = Tezos.get_sender () in + let liquidity_owner = Option.unopt_with_error (Big_map.find_opt owner s.sell_ctez.liquidity_owners) "no liquidity owner" in + let () = assert_with_error (liquidity_owner.lpt >= param.lpt) "insufficient liquidity" in + let () = assert_with_error (ctez_removed >= param.minCtezReceived) "insufficient ctez would be received" in + + (* compute the amount of tez to receive after netting the owed amount *) + let tez_to_receive = tez_removed - liquidity_owner.owed in + let () = assert_with_error (tez_to_receive >= int param.minTezReceived) "insufficient tez would be received" in + let (owed, tez_to_receive) = + if tez_to_receive < 0 then + (abs (liquidity_owner.owed - tez_removed), 0n) + else + (0n, abs tez_to_receive) + in + (* computed the amount of subsidy to recieve after netting the owed subsidy amount *) + + + let subsidy_to_receive = subsidy_removed - liquidity_owner.subsidy_owed in + let () = assert_with_error (subsidy_to_receive >= int param.minSubsidyReceived) "insufficient subsidy would be received" in + let (subsidy_owed, subsidy_to_receive) = + if subsidy_to_receive < 0 then + (abs (liquidity_owner.subsidy_owed - subsidy_removed), 0n) + else + (0n, abs subsidy_to_receive) + in + + let liquidity_ower = { liquidity_owner with + lpt = abs (liquidity_owner.lpt - param.lpt) ; + owed = owed ; + subsidy_owed = subsidy_owed } in + let liquidity_owners = Big_map.update owner (Some liquidity_owner) s.sell_ctez.liquidity_owners in + + let sell_ctez = {s.sell_ctez with + liquidity_owners = liquidity_owners ; + total_lpt = abs (s.sell_ctez.total_lpt - param.lpt) ; + total_liquidity = abs (s.sell_ctez.total_liquidity - ctez_removed) ; + total_proceeds = abs (s.sell_ctez.total_proceeds - tez_removed) ; + total_subsidy = abs (s.sell_ctez.total_subsidy - subsidy_removed) ; + } in + let receive_ctez = Tezos.transaction (param.to_, (s.ctez_token_contract, ctez_removed)) 0mutez s.liquidity_dex_address in + let receive_subsidy = Tezos.transaction (param.to_, (s.ctez_token_contract, subsidy_to_receive)) 0mutez s.liquidity_dex_address in + let receive_tez = Tezos.transaction () (tez_to_receive * 1mutez) param.to_ in + ({s with sell_ctez = sell_ctez}, [receive_ctez; receive_subsidy; receive_tez]) + + +let min (x : nat) (y : nat) : nat = if x < y then x else y + +let clamp_nat (x : int) : nat = + match is_nat x with + | None -> 0n + | Some x -> x + +let newton_step (q : nat) (t : nat) (_Q : nat) (dq : nat): int = + (* + (3 dq⁴ + 6 dq² (q - Q)² + 8 dq³ (-q + Q) + 80 Q³ t) / (4 ((dq - q)³ + 3 (dq - q)² Q + 3 (dq - q) Q² + 21 Q³)) + todo, check that implementation below is correct + TODO: optimize the computation of [q - _Q] and other constants + (A dq^2 +B)/(C + dq(D+dq(4dq-E))) + *) + // ensures that dq < q + let dq = min dq q in + // assert q < _Q (due to clamp at [invert]) + let q_m_Q = q - _Q in + + let dq_m_q = dq - q in + let dq_m_q_sq = dq_m_q * dq_m_q in + let dq_m_q_cu = dq_m_q_sq * dq_m_q in + let _Q_sq = _Q * _Q in + let _Q_cu = _Q_sq * _Q in + + let num = 3 * dq * dq * dq * dq + 6 * dq * dq * q_m_Q * q_m_Q + 8 * dq * dq * dq * (-q_m_Q) + 80 * _Q_cu * t in + let denom = 4 * (dq_m_q_cu + 3 * dq_m_q_sq * _Q + 3 * dq_m_q * _Q_sq + 21 * _Q_cu) in + + num / denom + +let invert (q : nat) (t : nat) (_Q : nat) : nat = + (* q is the current amount, + t is the amount you want to trade + _Q is the target amount + *) + (* note that the price is generally very nearly linear, after all the worth marginal price is 1.05, so Newton + converges stupidly fast *) + let q = min q _Q in + let dq = clamp_nat (newton_step q t _Q t) in + let dq = clamp_nat (newton_step q t _Q dq) in + let dq = clamp_nat (newton_step q t _Q dq) in + let result = dq - dq / 1_000_000_000 - 1 in + match is_nat result with + | None -> failwith "trade size too small" + | Some x -> x + + +let append t1 t2 = List.fold_right (fun (x, tl) -> x :: tl) t1 t2 + +[@entry] +let tez_to_ctez (param : tez_to_ctez) (s : storage) : operation list * storage = + let update_ops, s = update_ctez_contract_if_needed s in + + let () = assert_with_error (Tezos.get_now () <= param.deadline) "deadline has passed" in + (* The amount of tez that will be bought is calculated by integrating a polynomial which is a function of the fraction u purchased over q + * the polynomial, representing the marginal price is given as (21 - 3 * u + 3 u^2 - u^3) / 20 + * again, u is the quantity of ctez purchased over q which represents this characteristic quantity of ctez in the ctez half dex.&& + * the integral of this polynomial between u = 0 and u = x / q (where x will be ctez_to_sell) is is given as + * (21 * u - 3 * u^2 / 2 + u^3 - u^4 / 4) / 20 + * or (cts(cts(cts^2-3q^2)+42 q^3))/(40q^4) *) +// let cts = ctez_to_sell in let q = s.q in +// let q2 = q * q in +// let d_tez = (cts * (cts * (cts * cts - 3 * q2) + 42 * q * q2)) / (40 * q2 * q2) in + + let t = Bitwise.shift_left (Tezos.get_amount () / 1mutez) 48n / s.target in + let ctez_to_sell = invert s.sell_ctez.total_liquidity t s._Q in + let () = assert_with_error (ctez_to_sell >= param.minCtezBought) "insufficient ctez would be bought" in + let () = assert_with_error (ctez_to_sell <= s.sell_ctez.total_liquidity) "insufficient ctez in the dex" in + // Update dex + let half_dex = s.sell_ctez in + let half_dex: half_dex = { half_dex with total_liquidity = clamp_nat (half_dex.total_liquidity - ctez_to_sell); total_proceeds = half_dex.total_proceeds + (Tezos.get_amount () / 1mutez) } in + // Transfer ctez to the buyer + let fa_contract = (Tezos.get_entrypoint "%transfer" s.ctez_token_contract : fa12_transfer contract) in + let receive_ctez = Tezos.transaction { address_from = s.liquidity_dex_address; address_to = param.to_; value = ctez_to_sell } 0mutez fa_contract in + // Deal with subsidy later + (append update_ops [receive_ctez], {s with sell_ctez = half_dex}) + + +let implicit_transfer (to_ : address) (amt : tez) : operation = + let contract = (Tezos.get_entrypoint "%default" to_ : unit contract) in + Tezos.transaction () amt contract + +let ctez_transfer (s : storage) (to_ : address) (value: nat) : operation = + let fa_contract = (Tezos.get_entrypoint "%transfer" s.ctez_token_contract : fa12_transfer contract) in + let receive_ctez = Tezos.transaction { address_from = s.liquidity_dex_address; address_to = to_; value } 0mutez fa_contract in + receive_ctez + + +[@entry] +let withdraw_for_ctez_half_dex (param : withdraw_for_ctez_half_dex) (s: storage) : operation list * storage = + // withdraw: you can withdraw x so long as x + owed < lpt * total_proceeds / total_lpt, after which owed := owed + x + // So, my thoughts on withdrawing: + // you can withdraw x, so long as x + owe < lpt * total_proceeds / total_lpt + // owe := owe + x + // 2:57 PM + // owe never decreases, it's basically a tally of everything you've ever withdrawn + // 2:57 PM + // so when you add liquidity, it's like you added all those proceeds, and then withdrew lpt * total_proceeds / total_lpt + // + // TL;DR: proceeds = tez + total owed; proceeds doesn't increase + + let owner = Tezos.get_sender () in + let half_dex = s.sell_ctez in + let liquidity_owner = Option.value_with_error "no liquidity owner" (Big_map.find_opt owner half_dex.liquidity_owners) in + let share_of_proceeds = liquidity_owner.lpt * half_dex.total_proceeds / half_dex.total_lpt in + // proceeds in tez + let amount_proceeds_withdrawn = clamp_nat (share_of_proceeds - liquidity_owner.owed) in + let share_of_subsidy = liquidity_owner.lpt * half_dex.total_subsidy / half_dex.total_lpt in + // subsidy in ctez + let amount_subsidy_withdrawn = clamp_nat (share_of_subsidy - liquidity_owner.subsidy_owed) in + // liquidity owner owes the full share of proceeds + let liquidity_owner = { liquidity_owner with owed = share_of_proceeds; subsidy_owed = share_of_subsidy } in + // update half dex + let half_dex = { half_dex with liquidity_owners = Big_map.update owner (Some liquidity_owner) half_dex.liquidity_owners } in + // do transfers + let receive_proceeds = implicit_transfer param.to_ (amount_proceeds_withdrawn * 1mutez) in + let receive_subsidy = ctez_transfer s param.to_ amount_subsidy_withdrawn in + ([receive_proceeds; receive_subsidy], {s with sell_ctez = half_dex}) + + + + +let main (p, s : parameter * storage) : result = + match p with + | Withdraw w -> (withdraw s w : result) + | Register_oven_deposit r -> (register_oven_deposit s r : result) + | Create d -> (create s d : result) + | Liquidate l -> (liquidate s l : result) + | Mint_or_burn xs -> (mint_or_burn s xs : result) + | Cfmm_info ((x,y),z) -> (cfmm_info s x y z : result) + | Set_addresses xs -> (set_addresses s xs : result) + | Get_target t -> (get_target s t : result) + + diff --git a/limit_dex.mligo b/limit_dex.mligo index 1cb12de2..7d91f88b 100644 --- a/limit_dex.mligo +++ b/limit_dex.mligo @@ -1,3 +1,165 @@ +// TODO: +// - Model of ctez +// - Swap function +// - Handle subsidy + + + +[@inline] +let ceildiv (numerator : nat) (denominator : nat) : nat = abs ((- numerator) / (int denominator)) + + +// module Half_dex = struct +// (** A half dex is defined by: +// - An ordered liquidity share [(tau_0, tau_1)] +// - A reserve of the 'self' token [r0 : tau_0] +// - A reserve of the 'proceeds' token [r1 : tau_1] +// Each account may own some shares in the dex. +// *) + +// let transfer_self (from_ : address) (to_ : address) (value : nat) : operation = +// failwith "TODO" + +// type liquidity_owner = +// { liquidity_shares : nat (** the amount of liquidity shares owned by the account. *) +// ; proceeds_owed : nat (** the amount of the proceeds token owed to the dex by the account. *) +// ; subsidy_owed : nat (** the amount of ctez subsidy owed to the dex by the account. *) +// } + +// let default_liqudity_owner = { liquidity_shares = 0n ; proceeds_owed = 0n ; subsidy_owed = 0n } + +// type t = +// { liquidity_owners : (address, liquidity_owner) big_map (** map of liquidity owners. *) +// ; total_liquidity_shares : nat (** total amount of liquidity shares. *) +// ; self_reserves : nat (** total amount of liquidity. *) +// ; proceeds_reserves : nat (** total amount accumulated from proceeds. *) +// } + + +// type deposit = +// { owner: address (** the address that will own the liquidity shares. *) +// ; amount_deposited: nat (** the amount of the 'self' token to deposit. *) +// ; min_liquidity: nat (** the minimum amount of liquidity shares to add. *) +// ; deadline : timestamp (** the deadline for the transaction. *) +// } + + +// [@inline] +// let redeem_amount (x : nat) (reserve : nat) (total : nat) : nat = +// (* The redeem rate is defined as +// RX_i(t_0, t_1) := r_i / total(t_0, t_1) +// *) +// ceildiv (x * reserve) total + +// [@inline] +// let redeem_amount_inverted (lqt : nat) (reserve: nat) (total: nat) : nat = +// // The redeem amount is defined as +// // lqt = x RX_i(t_0, t_1) +// // Thus +// // x = (lqt * total(t_0, t_1)) / reserve +// ceildiv (lqt * total) reserve + +// let find_liquidity_owner (t : t) (owner : address) : liquidity_owner = +// Option.value default_liqudity_owner (Big_map.find_opt owner t.liquidity_owners) + +// let set_liquidity_owner (t : t) (owner : address) (liquidity_owner : liquidity_owner) : t = +// { t with liquidity_owners = Big_map.update owner (Some liquidity_owner) t.liquidity_owners } + +// let update_liquidity_owner (t : t) (owner : address) (f : liquidity_owner -> liquidity_owner) : t = +// let liquidity_owner = find_liquidity_owner t owner in +// let liquidity_owner = f liquidity_owner in +// set_liquidity_owner t owner liquidity_owner + +// let deposit (t : t) ({ owner; amount_deposited; min_liquidity; deadline } : deposit) () +// : t * operation list +// = +// let d_liquidity = +// redeem_amount_inverted amount_deposited t.self_reserves t.total_liquidity_shares +// in +// let () = +// assert_with_error +// (d_liquidity >= min_liquidity) +// "transaction would create insufficient liquidity" +// in +// let () = +// assert_with_error +// (Tezos.get_now () <= deadline) +// "deadline has passed" +// in +// let d_proceeds = +// redeem_amount d_liquidity t.proceeds_reserves t.total_liquidity_shares +// in +// let t = +// update_liquidity_owner t owner (fun liquidity_owner -> +// { liquidity_owner with +// liquidity_shares = liquidity_owner.liquidity_shares + d_liquidity +// ; proceeds_owed = liquidity_owner.proceeds_owed + d_proceeds +// }) +// in +// let t = +// { t with +// total_liquidity_shares = t.total_liquidity_shares + d_liquidity +// ; self_reserves = t.self_reserves + amount_deposited +// } +// in +// let receive_self = transfer_self owner (Tezos.get_self_address ()) amount_deposited in +// t, [ receive_self ] + + +// type redeem = +// { to_: address (** the address to receive the tokens *) +// ; liquidity_redeemed : nat (** the amount of liquidity shares to redeem *) +// ; min_self_received : nat (* minimum amount of tez to receive *) +// ; min_proceeds_received : nat (* minimum amount of ctez to receive *) +// ; min_subsidy_received : nat (* minimum amount of ctez subsidy to receive *) +// ; deadline : timestamp (* deadline for the transaction *) +// } + +// let redeem (t : t) ({ to_; liquidity_redeemed; min_self_received; min_proceeds_received; min_subsidy_received; deadline } : redeem) : t * operation list = +// let () = assert_with_error (Tezos.get_now () <= deadline) "deadline has passed" in +// let owner = Tezos.get_sender () in +// let liquidity_owner = find_liquidity_owner t owner in +// let () = assert_with_error (liquidity_owner.liquidity_shares >= liquidity_redeemed) "insufficient liquidity" in +// let self_redeemed = redeem_amount liquidity_redeemed t.self_reserves t.total_liquidity_shares in +// let owed_proceeds, proceeds_redeemed = +// (* Proceeds must account for netting the owed amount *) +// let v = redeem_amount liquidity_redeemed t.proceeds_reserves t.total_liquidity_shares in +// if v < liquidity_owner.proceeds_owed then +// (abs (liquidity_owner.proceeds_owed - v), 0n) +// else +// (0n, abs (v - liquidity_owner.proceeds_owed)) +// in +// let () = assert_with_error (proceeds_redeemed >= min_proceeds_received) "insufficient proceeds would be received" in +// let t = update_liquidity_owner t owner (fun liquidity_owner -> +// { liquidity_owner with +// liquidity_shares = abs (liquidity_owner.liquidity_shares - liquidity_redeemed) +// ; proceeds_owed = liquidity_owner.proceeds_owed + owed_proceeds +// }) +// in +// let t = { t with +// total_liquidity_shares = abs (t.total_liquidity_shares - liquidity_redeemed) +// ; self_reserves = abs (t.self_reserves - self_redeemed) +// ; proceeds_reserves = abs (t.proceeds_reserves - proceeds_redeemed) +// } +// in +// let receive_ctez = Tezos.transaction (param.to_, (s.ctez_token_contract, ctez_removed)) 0mutez s.liquidity_dex_address in +// let receive_subsidy = Tezos.transaction (param.to_, (s.ctez_token_contract, subsidy_to_receive)) 0mutez s.liquidity_dex_address in +// let receive_tez = Tezos.transaction () (tez_to_receive * 1mutez) param.to_ in +// (t, [receive_ctez; receive_subsidy; receive_tez]) + + +// type swap = +// { to_: address (** address that will own the 'self' tokens in the swap *) +// ; deadline : timestamp (** deadline for the transaction *) +// ; proceeds_amount : nat (** minimum amount of tez to buy *) +// ; min_self : nat (** the *) +// } + +// let swap (t : t) +// end + + + type add_tez_liquidity = [@layout:comb] { diff --git a/oven.mligo b/oven.mligo index b5cc3557..2df6301b 100644 --- a/oven.mligo +++ b/oven.mligo @@ -1,6 +1,6 @@ #include "oven_types.mligo" -let create_oven (delegate : key_hash option) (amnt : tez) (storage : oven_storage) = Tezos.create_contract +let originate_oven (delegate : key_hash option) (amnt : tez) (storage : oven_storage) = Tezos.create_contract (* Contract code for an oven *) (fun (p , s : oven_parameter * oven_storage) -> ( (* error codes *) @@ -30,8 +30,8 @@ let create_oven (delegate : key_hash option) (amnt : tez) (storage : oven_storag | Whitelist depositors -> Set.mem (Tezos.get_sender ()) depositors ) then let register = ( - match (Tezos.get_entrypoint_opt "%register_deposit" s.admin : (register_deposit contract) option) with - | None -> (failwith error_CANNOT_FIND_REGISTER_DEPOSIT_ENTRYPOINT : register_deposit contract) + match (Tezos.get_entrypoint_opt "%register_deposit" s.admin : (register_oven_deposit contract) option) with + | None -> (failwith error_CANNOT_FIND_REGISTER_DEPOSIT_ENTRYPOINT : register_oven_deposit contract) | Some register -> register) in (([ Tezos.transaction {amount = Tezos.get_amount () ; handle = s.handle} 0mutez register] : operation list), s) else diff --git a/oven_types.mligo b/oven_types.mligo index f69dccc4..b593d6e4 100644 --- a/oven_types.mligo +++ b/oven_types.mligo @@ -16,7 +16,7 @@ type depositors = | Whitelist of address set type oven_handle = [@layout:comb] {id : nat ; owner : address} -type register_deposit = [@layout:comb] { handle : oven_handle ; amount : tez } +type register_oven_deposit = [@layout:comb] { handle : oven_handle ; amount : tez } type oven_storage = { From 6d0b72096d3d088b3b21c76eaa374bace30b4b5a Mon Sep 17 00:00:00 2001 From: Alistair O'Brien Date: Thu, 2 May 2024 12:49:14 +0100 Subject: [PATCH 92/92] separate half dex into parameterised module --- context.mligo | 24 +++ ctez_2.mligo | 416 ++++--------------------------------------------- half_dex.mligo | 348 +++++++++++++++++++++++++++++++++++++++++ stdctez.mligo | 28 ++++ 4 files changed, 431 insertions(+), 385 deletions(-) create mode 100644 context.mligo create mode 100644 half_dex.mligo create mode 100644 stdctez.mligo diff --git a/context.mligo b/context.mligo new file mode 100644 index 00000000..c32504c0 --- /dev/null +++ b/context.mligo @@ -0,0 +1,24 @@ +#include "stdctez.mligo" + +type t = + { target : nat + ; drift : int + ; _Q : nat (* Q is the desired quantity of ctez in the ctez half dex, + floor(Q * target) is the desired quantity of tez in the tez half dex *) + ; ctez_fa12_address : address + } + +let transfer_xtz (to_ : address) (amount : nat) : operation = + let contract = (Tezos.get_entrypoint "%default" to_ : unit contract) in + Tezos.transaction () (amount * 1mutez) contract + + +type fa12_transfer = + { [@annot:from] from_ : address + ; [@annot:to] to_ : address + ; value : nat + } + +let transfer_ctez (t : t) (from_ : address) (to_ : address) (value : nat) : operation = + let contract = (Tezos.get_entrypoint "%transfer" t.ctez_fa12_address : fa12_transfer contract) in + Tezos.transaction { from_; to_; value } 0mutez contract \ No newline at end of file diff --git a/ctez_2.mligo b/ctez_2.mligo index 07797586..003de52f 100644 --- a/ctez_2.mligo +++ b/ctez_2.mligo @@ -1,67 +1,35 @@ -(* - Order of deployment - 1. Deploy the oven management contract (this contract) - 2. Deploy the fa12 address for the ctez contract, setting the oven management address as admin - 3. Deploy the CFMM, hard coding the oven management contract address as consumer - 4. Deploy the FA12 for the LQT specifying the CFMM as admin - 5. Manually set the LQT FA12 address in the CFMM - 6. Manually set the ctez fa12 address and the cfmm address in the oven management contract -*) - #include "oven_types.mligo" - - - - -(* End of oven types *) - - -type liquidate = [@layout:comb] { handle : oven_handle ; quantity : nat ; [@annot:to] to_ : unit contract } -type mint_or_burn = [@layout:comb] {id : nat ; quantity : int} - -type parameter = - | Create of create - | Withdraw of withdraw - | Liquidate of liquidate - | Register_oven_deposit of register_oven_deposit - | Mint_or_burn of mint_or_burn - | Cfmm_info of (nat * nat) * nat - | Set_addresses of set_addresses - | Get_target of nat contract - -type oven = {tez_balance : tez ; ctez_outstanding : nat ; address : address ; fee_index : nat} - - -type liquidity_owner = -{ - lpt : nat ; (* LP token amount *) - owed : nat ; (* amount of the proceeds token owed to the contract *) - subsidy_owed : nat ; (* amount of ctez subsidy owed to the contract *) -} - -type half_dex = -{ - liquidity_owners : (address, liquidity_owner) big_map ; (* map of liquidity owners *) - total_lpt : nat ; (* total amount of liquidity tokens *) - total_liquidity : nat ; (* total amount of liquidity *) - total_proceeds : nat ; (* total amount accumulated from proceeds *) - total_subsidy : nat ; (* total amount accumulated from subsidy *) - fee_index : nat ; -} - -type storage = { - ovens : (oven_handle, oven) big_map ; - target : nat ; - drift : int ; - last_update : timestamp ; - ctez_fa12_address : address ; (* address of the fa12 contract managing the ctez token *) - sell_ctez : half_dex ; - sell_tez : half_dex ; - _Q : nat ; (* Q is the desired quantity of ctez in the ctez half dex, - floor(Q * target) is the desired quantity of tez in the tez half dex *) -} - -type result = (operation list) * storage +#include "stdctez.mligo" +#import "half_dex.mligo" Half_dex +#import "context.mligo" Context + +// TODO: Hook up half dex here + +type liquidate = + { handle : oven_handle + ; quantity : nat + ; [@annot:to] to_ : unit contract + } + +type mint_or_burn = + { id : nat + ; quantity : int + } + +type oven = + { tez_balance : tez + ; ctez_outstanding : nat + ; address : address + ; fee_index : nat + } + +type storage = + { ovens : (oven_handle, oven) big_map + ; last_update : timestamp + ; sell_ctez : Half_dex.t + ; sell_tez : Half_dex.t + ; context : Context.t + } (* Errors *) @@ -293,325 +261,3 @@ let housekeeping () (storage : storage) : result = -type add_tez_liquidity = -[@layout:comb] -{ - owner : address ; (* address that will own the liqudity *) - minLiquidity : nat ; (* minimum amount of liquidity to add *) - deadline : timestamp ; (* deadline for the transaction *) -} - -type add_ctez_liquidity = -[@layout:comb] -{ - owner : address ; (* address that will own the liqudity *) - minLiquidity : nat ; (* minimum amount of liquidity to add *) - deadline : timestamp ; (* deadline for the transaction *) - ctezDeposited : nat ; (* amount of ctez to deposit *) -} - -type remove_tez_liquidity = -[@layout:comb] -{ - [@annot:to] to_: address ; (* address to receive to *) - lpt : nat ; (* amount of liquidity to remove *) - minTezReceived : nat ; (* minimum amount of tez to receive *) - minCtezReceived : nat ; (* minimum amount of ctez to receive *) - minSubsidyReceived : nat ; (* minimum amount of ctez subsidy to receive *) - deadline : timestamp ; (* deadline for the transaction *) -} - -type remove_ctez_liquidity = -[@layout:comb] -{ - [@annot:to] to_: address ; (* address to receive to *) - lpt : nat ; (* amount of liquidity to remove *) - minTezReceived : nat ; (* minimum amount of tez to receive *) - minCtezReceived : nat ; (* minimum amount of ctez to receive *) - minSubsidyReceived : nat ; (* minimum amount of ctez subsidy to receive *) - deadline : timestamp ; (* deadline for the transaction *) -} - -type tez_to_ctez = -[@layout:comb] -{ - [@annot:to] to_: address ; (* address that will own the ctez *) - deadline : timestamp ; (* deadline for the transaction *) - minCtezBought : nat ; (* minimum amount of ctez to buy *) -} - -type ctez_to_tez = -[@layout:comb] -{ - [@annot:to] to_: address ; (* address that will own the tez *) - deadline : timestamp ; (* deadline for the transaction *) - minTezBought : nat ; (* minimum amount of tez to buy *) - ctezSold : nat ; (* amount of ctez to sell *) -} - -type withdraw_for_tez_liquidity = -[@layout:comb] -{ - [@annot:to] to_: address ; (* address to withdraw to *) -} - -type withdraw_for_ctez_half_dex = -[@layout:comb] -{ - [@annot:to] to_: address ; (* address to withdraw to, note that here you receive both ctez and tez - because ctez is received as part of the subsidy *) -} - - -type fa12_transfer = - [@layout:comb] - { [@annot:from] address_from : address; - [@annot:to] address_to : address; - value : nat } - - -// retrieve _Q and target - - - -let update_ctez_contract_if_needed (s : storage) : operation list * storage = - let curr_level = Tezos.get_level () in - if s.last_update <> curr_level then - let ctez_contract = (Tezos.get_entrypoint "%dex_update" s.ctez_contract : (nat * nat) contract) in - let operation = Tezos.transaction (s.sell_ctez.total_liquidity, s.sell_tez.total_liquidity) 0mutez ctez_contract in - let target , _Q = Option.value_with_error "dex_info entrypoint must exist" (Tezos.call_view "%dex_info" () s.ctez_contract) in - ([operation], {s with last_update = curr_level; target = target; _Q = _Q}) - else - ([], s) - - -[@inline] -let ceildiv (numerator : nat) (denominator : nat) : nat = abs ((- numerator) / (int denominator)) - - -[@inline] -let redeem_amount (x : nat) (reserve : nat) (total : nat) : nat = - // The redeem rate is defined as - // RX_i(t_0, t_1) := r_i / total(t_0, t_1) - // The redeem amount is defined as - // v = x / RX_i(t_0, t_1) = (x * total(t_0, t_1)) / reserve - (x * total) / reserve - - -[@entry] -let add_ctez_liquidity (param : add_ctez_liquidity) (s : storage) : storage * operation list = - let d_lpt = redeem_amount param.ctezDeposited s.sell_ctez.total_liquidity s.sell_ctez.total_lpt in - let () = assert_with_error (d_lpt >= param.minLiquidity) "transaction would create insufficient liquidity" in - let () = assert_with_error (Tezos.get_now () <= param.deadline) "deadline has passed" in - // lpt is going to be lpt + d_lpt - // ctez is going to be ctez + d_ctez - // if the owner already has liquidity, we need to update the owed amount - // otherwise we need to create a new liquidity owner - let liquidity_owner = - Option.value - { lpt = 0n ; owed = 0n ; subsidy_owed = 0n} - (Big_map.find_opt param.owner s.sell_ctez.liquidity_owners) - in - let d_tez = ceildiv (s.sell_ctez.total_proceeds * d_lpt) s.sell_ctez.total_lpt in - let d_subsidy_owed = ceildiv (s.sell_ctez.total_subsidy * d_lpt) s.sell_ctez.total_lpt in - // Update liquidity owner - let liquidity_owner = { liquidity_owner with - lpt = liquidity_owner.lpt + d_lpt ; - owed = liquidity_owner.owed + d_tez ; - subsidy_owed = liquidity_owner.subsidy_owed + d_subsidy_owed } in - let liquidity_owners = Big_map.update param.owner (Some liquidity_owner) s.sell_ctez.liquidity_owners in - - let sell_ctez = {s.sell_ctez with - liquidity_owners = liquidity_owners ; - total_lpt = s.sell_ctez.total_lpt + d_lpt ; - total_liquidity = s.sell_ctez.total_liquidity + param.ctezDeposited ; - } in - - let receive_ctez = Tezos.transaction (param.owner, (s.liquidity_dex_address, param.ctezDeposited)) 0mutez s.ctez_token_contract in - ({s with sell_ctez = half_dex}, [receive_ctez]) - -[@entry] -let remove_ctez_liquidity (param : remove_ctez_liquidity) (s : storage) : storage * operation list = - let () = assert_with_error (Tezos.get_now () <= param.deadline) "deadline has passed" in - let ctez_removed = (param.lpt * s.sell_ctez.total_liquidity) / s.sell_ctez.total_lpt in - let tez_removed = (param.lpt * s.sell_ctez.total_proceeds) / s.sell_ctez.total_lpt in - let subsidy_removed = (param.lpt * s.sell_ctez.total_subsidy) / s.sell_ctez.total_lpt in - let owner = Tezos.get_sender () in - let liquidity_owner = Option.unopt_with_error (Big_map.find_opt owner s.sell_ctez.liquidity_owners) "no liquidity owner" in - let () = assert_with_error (liquidity_owner.lpt >= param.lpt) "insufficient liquidity" in - let () = assert_with_error (ctez_removed >= param.minCtezReceived) "insufficient ctez would be received" in - - (* compute the amount of tez to receive after netting the owed amount *) - let tez_to_receive = tez_removed - liquidity_owner.owed in - let () = assert_with_error (tez_to_receive >= int param.minTezReceived) "insufficient tez would be received" in - let (owed, tez_to_receive) = - if tez_to_receive < 0 then - (abs (liquidity_owner.owed - tez_removed), 0n) - else - (0n, abs tez_to_receive) - in - (* computed the amount of subsidy to recieve after netting the owed subsidy amount *) - - - let subsidy_to_receive = subsidy_removed - liquidity_owner.subsidy_owed in - let () = assert_with_error (subsidy_to_receive >= int param.minSubsidyReceived) "insufficient subsidy would be received" in - let (subsidy_owed, subsidy_to_receive) = - if subsidy_to_receive < 0 then - (abs (liquidity_owner.subsidy_owed - subsidy_removed), 0n) - else - (0n, abs subsidy_to_receive) - in - - let liquidity_ower = { liquidity_owner with - lpt = abs (liquidity_owner.lpt - param.lpt) ; - owed = owed ; - subsidy_owed = subsidy_owed } in - let liquidity_owners = Big_map.update owner (Some liquidity_owner) s.sell_ctez.liquidity_owners in - - let sell_ctez = {s.sell_ctez with - liquidity_owners = liquidity_owners ; - total_lpt = abs (s.sell_ctez.total_lpt - param.lpt) ; - total_liquidity = abs (s.sell_ctez.total_liquidity - ctez_removed) ; - total_proceeds = abs (s.sell_ctez.total_proceeds - tez_removed) ; - total_subsidy = abs (s.sell_ctez.total_subsidy - subsidy_removed) ; - } in - let receive_ctez = Tezos.transaction (param.to_, (s.ctez_token_contract, ctez_removed)) 0mutez s.liquidity_dex_address in - let receive_subsidy = Tezos.transaction (param.to_, (s.ctez_token_contract, subsidy_to_receive)) 0mutez s.liquidity_dex_address in - let receive_tez = Tezos.transaction () (tez_to_receive * 1mutez) param.to_ in - ({s with sell_ctez = sell_ctez}, [receive_ctez; receive_subsidy; receive_tez]) - - -let min (x : nat) (y : nat) : nat = if x < y then x else y - -let clamp_nat (x : int) : nat = - match is_nat x with - | None -> 0n - | Some x -> x - -let newton_step (q : nat) (t : nat) (_Q : nat) (dq : nat): int = - (* - (3 dq⁴ + 6 dq² (q - Q)² + 8 dq³ (-q + Q) + 80 Q³ t) / (4 ((dq - q)³ + 3 (dq - q)² Q + 3 (dq - q) Q² + 21 Q³)) - todo, check that implementation below is correct - TODO: optimize the computation of [q - _Q] and other constants - (A dq^2 +B)/(C + dq(D+dq(4dq-E))) - *) - // ensures that dq < q - let dq = min dq q in - // assert q < _Q (due to clamp at [invert]) - let q_m_Q = q - _Q in - - let dq_m_q = dq - q in - let dq_m_q_sq = dq_m_q * dq_m_q in - let dq_m_q_cu = dq_m_q_sq * dq_m_q in - let _Q_sq = _Q * _Q in - let _Q_cu = _Q_sq * _Q in - - let num = 3 * dq * dq * dq * dq + 6 * dq * dq * q_m_Q * q_m_Q + 8 * dq * dq * dq * (-q_m_Q) + 80 * _Q_cu * t in - let denom = 4 * (dq_m_q_cu + 3 * dq_m_q_sq * _Q + 3 * dq_m_q * _Q_sq + 21 * _Q_cu) in - - num / denom - -let invert (q : nat) (t : nat) (_Q : nat) : nat = - (* q is the current amount, - t is the amount you want to trade - _Q is the target amount - *) - (* note that the price is generally very nearly linear, after all the worth marginal price is 1.05, so Newton - converges stupidly fast *) - let q = min q _Q in - let dq = clamp_nat (newton_step q t _Q t) in - let dq = clamp_nat (newton_step q t _Q dq) in - let dq = clamp_nat (newton_step q t _Q dq) in - let result = dq - dq / 1_000_000_000 - 1 in - match is_nat result with - | None -> failwith "trade size too small" - | Some x -> x - - -let append t1 t2 = List.fold_right (fun (x, tl) -> x :: tl) t1 t2 - -[@entry] -let tez_to_ctez (param : tez_to_ctez) (s : storage) : operation list * storage = - let update_ops, s = update_ctez_contract_if_needed s in - - let () = assert_with_error (Tezos.get_now () <= param.deadline) "deadline has passed" in - (* The amount of tez that will be bought is calculated by integrating a polynomial which is a function of the fraction u purchased over q - * the polynomial, representing the marginal price is given as (21 - 3 * u + 3 u^2 - u^3) / 20 - * again, u is the quantity of ctez purchased over q which represents this characteristic quantity of ctez in the ctez half dex.&& - * the integral of this polynomial between u = 0 and u = x / q (where x will be ctez_to_sell) is is given as - * (21 * u - 3 * u^2 / 2 + u^3 - u^4 / 4) / 20 - * or (cts(cts(cts^2-3q^2)+42 q^3))/(40q^4) *) -// let cts = ctez_to_sell in let q = s.q in -// let q2 = q * q in -// let d_tez = (cts * (cts * (cts * cts - 3 * q2) + 42 * q * q2)) / (40 * q2 * q2) in - - let t = Bitwise.shift_left (Tezos.get_amount () / 1mutez) 48n / s.target in - let ctez_to_sell = invert s.sell_ctez.total_liquidity t s._Q in - let () = assert_with_error (ctez_to_sell >= param.minCtezBought) "insufficient ctez would be bought" in - let () = assert_with_error (ctez_to_sell <= s.sell_ctez.total_liquidity) "insufficient ctez in the dex" in - // Update dex - let half_dex = s.sell_ctez in - let half_dex: half_dex = { half_dex with total_liquidity = clamp_nat (half_dex.total_liquidity - ctez_to_sell); total_proceeds = half_dex.total_proceeds + (Tezos.get_amount () / 1mutez) } in - // Transfer ctez to the buyer - let fa_contract = (Tezos.get_entrypoint "%transfer" s.ctez_token_contract : fa12_transfer contract) in - let receive_ctez = Tezos.transaction { address_from = s.liquidity_dex_address; address_to = param.to_; value = ctez_to_sell } 0mutez fa_contract in - // Deal with subsidy later - (append update_ops [receive_ctez], {s with sell_ctez = half_dex}) - - -let implicit_transfer (to_ : address) (amt : tez) : operation = - let contract = (Tezos.get_entrypoint "%default" to_ : unit contract) in - Tezos.transaction () amt contract - -let ctez_transfer (s : storage) (to_ : address) (value: nat) : operation = - let fa_contract = (Tezos.get_entrypoint "%transfer" s.ctez_token_contract : fa12_transfer contract) in - let receive_ctez = Tezos.transaction { address_from = s.liquidity_dex_address; address_to = to_; value } 0mutez fa_contract in - receive_ctez - - -[@entry] -let withdraw_for_ctez_half_dex (param : withdraw_for_ctez_half_dex) (s: storage) : operation list * storage = - // withdraw: you can withdraw x so long as x + owed < lpt * total_proceeds / total_lpt, after which owed := owed + x - // So, my thoughts on withdrawing: - // you can withdraw x, so long as x + owe < lpt * total_proceeds / total_lpt - // owe := owe + x - // 2:57 PM - // owe never decreases, it's basically a tally of everything you've ever withdrawn - // 2:57 PM - // so when you add liquidity, it's like you added all those proceeds, and then withdrew lpt * total_proceeds / total_lpt - // - // TL;DR: proceeds = tez + total owed; proceeds doesn't increase - - let owner = Tezos.get_sender () in - let half_dex = s.sell_ctez in - let liquidity_owner = Option.value_with_error "no liquidity owner" (Big_map.find_opt owner half_dex.liquidity_owners) in - let share_of_proceeds = liquidity_owner.lpt * half_dex.total_proceeds / half_dex.total_lpt in - // proceeds in tez - let amount_proceeds_withdrawn = clamp_nat (share_of_proceeds - liquidity_owner.owed) in - let share_of_subsidy = liquidity_owner.lpt * half_dex.total_subsidy / half_dex.total_lpt in - // subsidy in ctez - let amount_subsidy_withdrawn = clamp_nat (share_of_subsidy - liquidity_owner.subsidy_owed) in - // liquidity owner owes the full share of proceeds - let liquidity_owner = { liquidity_owner with owed = share_of_proceeds; subsidy_owed = share_of_subsidy } in - // update half dex - let half_dex = { half_dex with liquidity_owners = Big_map.update owner (Some liquidity_owner) half_dex.liquidity_owners } in - // do transfers - let receive_proceeds = implicit_transfer param.to_ (amount_proceeds_withdrawn * 1mutez) in - let receive_subsidy = ctez_transfer s param.to_ amount_subsidy_withdrawn in - ([receive_proceeds; receive_subsidy], {s with sell_ctez = half_dex}) - - - - -let main (p, s : parameter * storage) : result = - match p with - | Withdraw w -> (withdraw s w : result) - | Register_oven_deposit r -> (register_oven_deposit s r : result) - | Create d -> (create s d : result) - | Liquidate l -> (liquidate s l : result) - | Mint_or_burn xs -> (mint_or_burn s xs : result) - | Cfmm_info ((x,y),z) -> (cfmm_info s x y z : result) - | Set_addresses xs -> (set_addresses s xs : result) - | Get_target t -> (get_target s t : result) - - diff --git a/half_dex.mligo b/half_dex.mligo new file mode 100644 index 00000000..bceb76f7 --- /dev/null +++ b/half_dex.mligo @@ -0,0 +1,348 @@ +#include "stdctez.mligo" +#import "context.mligo" Context + +(** A half dex is defined by: + - An ordered liquidity share [(tau_0, tau_1)] + - A reserve of the 'self' token [r0 : tau_0] + - A reserve of the 'proceeds' token [r1 : tau_1] + - A subsidy owed by ovens to the dex (in ctez) [s : CTEZ] + - A fee index [f : nat] + Each account may own some shares in the dex. + + The dex is parameterised by a capability context for: + - transferring the self token + - transferring the proceeds token + - computing the target quantity of the 'self' token in the half dex +*) + +type environment = + { transfer_self : Context.t -> address -> address -> nat -> operation + ; transfer_proceeds : Context.t -> address -> nat -> operation + ; target_self_reserves : Context.t -> nat + } + +module Curve = struct + (** The marginal price [dp/du] is the derivative of the price function [p(u)] with respect to the + characteristic quantity [u = min(q/Q, 1)] where [q] is the current amount of the dex's 'self' + token and [Q] is the target amount. + + The marginal price function is given as [dp/du(u) = target * (21 - 3 * u + 3 * u^2 - u^3) / 20]. + Meaning the price of dex's 'self' token in 'proceed' token units is given by + {v + p(u_0 -> u_1) = ∫_{u_0}^{u_1} dp/du(u) du + = [target * (21 * u - 3 * u^2 / 2 + u^3 - u^4 / 4) / 20]_{u_1}^{u_0} + v} + for [u_1 < u_0] + + For a swap we need to determine [y] for a given [x] such that [p(q/q -> (q - y) / Q) = x] where + [x] is the amount of 'proceed' token units to be traded for [y] 'self' token units. + + Solving the above equation for [y] gives irrational solutions. We instead use Newton's method to + find an approximate solution. The Newton step is given as + {v + y_{i + 1} = y_i - p(q/Q, (q - y_i)/Q) / p'(q/Q, (q - y_i)/Q) + = FullySimplify[%] + = 3 y_i⁴ + 6 y_i² (q - Q)² + 8 y_i³ (-q + Q) + 80 Q³ x + ----------------------------------------------------------- + 4 ((y_i - q)³ + 3 (y_i - q)² Q + 3 (y_i - q) Q² + 21 Q³) + v} + + Note that since marginal price function is generally very nearly linear in the region [0, 1], + so Newton converges stupidly fast. + *) + + let newton_step (x : nat) (y : nat) (q : nat) (_Q : nat) : int = + (* Computes + 3 y⁴ + 6 y² (q - Q)² + 8 y³ (-q + Q) + 80 Q³ x + --------------------------------------------------- + 4 ((y - q)³ + 3 (y - q)² Q + 3 (y - q) Q² + 21 Q³) + *) + let dq = min y q in + let q_m_Q = q - _Q in + let dq_m_q = dq - q in + let dq_m_q_sq = dq_m_q * dq_m_q in + let dq_m_q_cu = dq_m_q_sq * dq_m_q in + let _Q_sq = _Q * _Q in + let _Q_cu = _Q_sq * _Q in + let num = 3 * dq * dq * dq * dq + 6 * dq * dq * q_m_Q * q_m_Q + 8 * dq * dq * dq * (-q_m_Q) + 80 * _Q_cu * x in + let denom = 4 * (dq_m_q_cu + 3 * dq_m_q_sq * _Q + 3 * dq_m_q * _Q_sq + 21 * _Q_cu) in + num / denom + + (** [swap_amount x q _Q] returns the swap amount for trading [x] units of the proceeds token + for a half dex with total quantity [q] and target quantity [_Q]. + *) + let swap_amount (x : nat) (q : nat) (_Q : nat) : nat = + let q = min q _Q in + (* Initial guess for [y] is [x] *) + let y = x in + let y = clamp_nat (newton_step x y q _Q) in + let y = clamp_nat (newton_step x y q _Q) in + let y = clamp_nat (newton_step x y q _Q) in + let result = y - y / 1_000_000_000 - 1 in + match is_nat result with + | None -> failwith "trade size too small" + | Some x -> x +end + +type liquidity_owner = + { liquidity_shares : nat (** the amount of liquidity shares owned by the account. *) + ; proceeds_owed : nat (** the amount of the proceeds token owed to the dex by the account. *) + ; subsidy_owed : nat (** the amount of ctez subsidy owed to the dex by the account. *) + } + +let default_liqudity_owner = + { liquidity_shares = 0n ; proceeds_owed = 0n ; subsidy_owed = 0n } + +type t = + { liquidity_owners : (address, liquidity_owner) big_map (** map of liquidity owners. *) + ; total_liquidity_shares : nat (** total amount of liquidity shares. *) + ; self_reserves : nat (** total amount of liquidity. *) + ; proceeds_reserves : nat (** total amount accumulated from proceeds. *) + ; subsidy_reserves : nat (** total amount accumulated from subsidy. *) + ; fee_index : Float48.t (** the fee index. *) + } + +let find_liquidity_owner (t : t) (owner : address) : liquidity_owner = + Option.value default_liqudity_owner (Big_map.find_opt owner t.liquidity_owners) + +let set_liquidity_owner (t : t) (owner : address) (liquidity_owner : liquidity_owner) : t = + { t with liquidity_owners = Big_map.update owner (Some liquidity_owner) t.liquidity_owners } + +let update_liquidity_owner (t : t) (owner : address) (f : liquidity_owner -> liquidity_owner) : t = + let liquidity_owner = find_liquidity_owner t owner in + let liquidity_owner = f liquidity_owner in + set_liquidity_owner t owner liquidity_owner + +type deposit = + { owner: address (** the address that will own the liquidity shares. *) + ; amount_deposited: nat (** the amount of the 'self' token to deposit. *) + ; min_liquidity: nat (** the minimum amount of liquidity shares to add. *) + ; deadline : timestamp (** the deadline for the transaction. *) + } + +[@inline] +let redeem_amount (x : nat) (reserve : nat) (total : nat) : nat = + (* The redeem rate is defined as + RX_i(t_0, t_1) := r_i / total(t_0, t_1) + *) + ceildiv (x * reserve) total + +[@inline] +let redeem_amount_inverted (lqt : nat) (reserve: nat) (total: nat) : nat = + // The redeem amount is defined as + // lqt = x RX_i(t_0, t_1) + // Thus + // x = (lqt * total(t_0, t_1)) / reserve + ceildiv (lqt * total) reserve + +let deposit + (t : t) + (ctxt : Context.t) + (env : environment) + ({ owner; amount_deposited; min_liquidity; deadline } : deposit) + () + : t with_operations + = + let d_liquidity = + redeem_amount_inverted amount_deposited t.self_reserves t.total_liquidity_shares + in + let () = + assert_with_error + (d_liquidity >= min_liquidity) + "transaction would create insufficient liquidity" + in + let () = assert_with_error (Tezos.get_now () <= deadline) "deadline has passed" in + let d_proceeds = + redeem_amount d_liquidity t.proceeds_reserves t.total_liquidity_shares + in + let d_subsidy = + redeem_amount d_liquidity t.subsidy_reserves t.total_liquidity_shares + in + let t = + update_liquidity_owner t owner (fun liquidity_owner -> + { liquidity_owner with + liquidity_shares = liquidity_owner.liquidity_shares + d_liquidity + ; proceeds_owed = liquidity_owner.proceeds_owed + d_proceeds + ; subsidy_owed = liquidity_owner.subsidy_owed + d_subsidy + }) + in + let t = + { t with + total_liquidity_shares = t.total_liquidity_shares + d_liquidity + ; self_reserves = t.self_reserves + amount_deposited + } + in + let receive_self = + env.transfer_self ctxt owner (Tezos.get_self_address ()) amount_deposited + in + [ receive_self ], t + + +type redeem = + { to_: address (** the address to receive the tokens *) + ; liquidity_redeemed : nat (** the amount of liquidity shares to redeem *) + ; min_self_received : nat (* minimum amount of tez to receive *) + ; min_proceeds_received : nat (* minimum amount of ctez to receive *) + ; min_subsidy_received : nat (* minimum amount of ctez subsidy to receive *) + ; deadline : timestamp (* deadline for the transaction *) + } + +[@inline] +let subtract_debt (debt : nat) (amt : nat) = + if amt < debt then (abs (debt - amt), 0n) + else (0n, abs (amt - debt)) + +let redeem + (t : t) + (ctxt : Context.t) + (env : environment) + ({ to_ + ; liquidity_redeemed + ; min_self_received + ; min_proceeds_received + ; min_subsidy_received + ; deadline + } : + redeem) + : t with_operations + = + let () = assert_with_error (Tezos.get_now () <= deadline) "deadline has passed" in + let owner = Tezos.get_sender () in + let liquidity_owner = find_liquidity_owner t owner in + let () = + assert_with_error + (liquidity_owner.liquidity_shares >= liquidity_redeemed) + "insufficient liquidity" + in + let self_redeemed = + redeem_amount liquidity_redeemed t.self_reserves t.total_liquidity_shares + in + let () = + assert_with_error + (self_redeemed >= min_self_received) + "insufficient self would be received" + in + let proceeds_owed, proceeds_redeemed = + subtract_debt + liquidity_owner.proceeds_owed + (redeem_amount liquidity_redeemed t.proceeds_reserves t.total_liquidity_shares) + in + let () = + assert_with_error + (proceeds_redeemed >= min_proceeds_received) + "insufficient proceeds would be received" + in + let subsidy_owed, subsidy_redeemed = + subtract_debt + liquidity_owner.subsidy_owed + (redeem_amount liquidity_redeemed t.subsidy_reserves t.total_liquidity_shares) + in + let () = + assert_with_error + (subsidy_redeemed >= min_subsidy_received) + "insufficient subsidy would be received" + in + let t = + update_liquidity_owner t owner (fun liquidity_owner -> + { liquidity_owner with + liquidity_shares = abs (liquidity_owner.liquidity_shares - liquidity_redeemed) + ; proceeds_owed + ; subsidy_owed + }) + in + let t = + { t with + total_liquidity_shares = abs (t.total_liquidity_shares - liquidity_redeemed) + ; self_reserves = abs (t.self_reserves - self_redeemed) + ; proceeds_reserves = abs (t.proceeds_reserves - proceeds_redeemed) + ; subsidy_reserves = abs (t.subsidy_reserves - subsidy_redeemed) + } + in + let receive_self = env.transfer_self ctxt (Tezos.get_self_address ()) to_ self_redeemed in + let receive_proceeds = + env.transfer_proceeds ctxt to_ proceeds_redeemed + in + let receive_subsidy = + Context.transfer_ctez ctxt (Tezos.get_self_address ()) to_ subsidy_redeemed + in + [ receive_self; receive_proceeds; receive_subsidy ], t + +type swap = + { to_: address (** address that will own the 'self' tokens in the swap *) + ; deadline : timestamp (** deadline for the transaction *) + ; proceeds_amount : nat (** the amount of the 'proceed' token used to buy the 'self' token *) + ; min_self : nat (** the minimum amount of 'self' tokens to receive *) + } + +let swap + (t : t) + (ctxt : Context.t) + (env : environment) + ({ to_; proceeds_amount; min_self; deadline } : swap) + : t with_operations + = + let () = assert_with_error (Tezos.get_now () <= deadline) "deadline has passed" in + let self_to_sell = Curve.swap_amount proceeds_amount t.self_reserves (env.target_self_reserves ctxt) in + let () = + assert_with_error (self_to_sell >= min_self) "insufficient 'self' would be bought" + in + let () = + assert_with_error (self_to_sell <= t.self_reserves) "insufficient 'self' in the dex" + in + let t = + { t with + self_reserves = abs (t.self_reserves - self_to_sell) + ; proceeds_reserves = t.proceeds_reserves + proceeds_amount + } + in + let receive_self = env.transfer_self ctxt (Tezos.get_self_address ()) to_ self_to_sell in + [ receive_self ], t + +type collect_proceeds_and_subsidy = + { to_: address } + +let collect_proceeds_and_subsidy + (t : t) + (ctxt : Context.t) + (env : environment) + ({ to_ } : collect_proceeds_and_subsidy) + : t with_operations + = + let owner = Tezos.get_sender () in + let liquidity_owner = find_liquidity_owner t owner in + (* compute withdrawable amount of proceeds *) + let share_of_proceeds = + redeem_amount + liquidity_owner.liquidity_shares + t.proceeds_reserves + t.total_liquidity_shares + in + let amount_proceeds_withdrawn = + clamp_nat (share_of_proceeds - liquidity_owner.proceeds_owed) + in + (* compute withdrawable amount of subsidy *) + let share_of_subsidy = + redeem_amount + liquidity_owner.liquidity_shares + t.subsidy_reserves + t.total_liquidity_shares + in + let amount_subsidy_withdrawn = + clamp_nat (share_of_subsidy - liquidity_owner.subsidy_owed) + in + let t = + set_liquidity_owner + t + owner + { liquidity_owner with + proceeds_owed = share_of_proceeds + ; subsidy_owed = share_of_subsidy + } + in + let receive_proceeds = + env.transfer_proceeds ctxt to_ amount_proceeds_withdrawn + in + let receive_subsidy = + Context.transfer_ctez ctxt (Tezos.get_self_address ()) to_ amount_subsidy_withdrawn + in + [ receive_proceeds; receive_subsidy ], t \ No newline at end of file diff --git a/stdctez.mligo b/stdctez.mligo new file mode 100644 index 00000000..37307371 --- /dev/null +++ b/stdctez.mligo @@ -0,0 +1,28 @@ +// Various helpful stdlib extensions for ctez + +type 'a with_operations = operation list * 'a + +[@inline] +let clamp_nat (x : int) : nat = + match is_nat x with + | None -> 0n + | Some x -> x + +[@inline] +let min (x : nat) (y : nat) : nat = if x < y then x else y + +[@inline] +let ceildiv (numerator : nat) (denominator : nat) : nat = abs ((- numerator) / (int denominator)) + +module Float48 = struct + type t = int + + // TODO +end + +module List = struct + include List + + [@inline] + let append t1 t2 = fold_right (fun (x, tl) -> x :: tl) t1 t2 +end \ No newline at end of file