From 80530b80d1c0b0012d3fba84ddcc668aaa54a4f3 Mon Sep 17 00:00:00 2001 From: Utku Demir Date: Thu, 17 Jun 2021 22:22:46 +1200 Subject: [PATCH 01/12] Make 'ptr' a 'nat' instead of 'int' --- src/ligo.ml | 4 ++++ src/ligo.mli | 1 + src/ptr.ml | 13 +++++++------ src/ptr.mli | 1 + 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/ligo.ml b/src/ligo.ml index 2b89bdad..4942bd44 100644 --- a/src/ligo.ml +++ b/src/ligo.ml @@ -143,6 +143,10 @@ let int_from_literal s = with exc -> failwith ("Ligo.int_from_literal: " ^ Printexc.to_string exc) let int_from_int64 = Z.of_int64 +let nat_from_int64 (t: Int64.t) = + let r = Z.of_int64 t in + assert (r > Z.zero); + r let add_int_int = Z.add diff --git a/src/ligo.mli b/src/ligo.mli index c4783fb4..c047b2a3 100644 --- a/src/ligo.mli +++ b/src/ligo.mli @@ -173,6 +173,7 @@ val key_hash_from_literal : String.t -> key_hash (* IN LIGO: type-annotate with val bytes_from_literal : String.t -> bytes (* IN LIGO: replace the double quotes with parens *) val int_from_int64: Int64.t -> int (* NON-LIGO, temporary*) +val nat_from_int64: Int64.t -> nat (* NON-LIGO, temporary*) val timestamp_from_seconds_literal : Int.t -> timestamp (* NON-LIGO: in LIGO they come from strings, or Tezos.now *) (* OPERATIONS ON int *) diff --git a/src/ptr.ml b/src/ptr.ml index 8521386c..ec5eb61d 100644 --- a/src/ptr.ml +++ b/src/ptr.ml @@ -1,11 +1,12 @@ -type ptr = Ligo.int +type ptr = Ligo.nat (* BEGIN_OCAML *) [@@deriving show] (* END_OCAML *) -let[@inline] ptr_null = Ligo.int_from_literal "0" -let[@inline] ptr_init = Ligo.int_from_literal "1" -let[@inline] ptr_next (t: ptr) = Ligo.add_int_int t (Ligo.int_from_literal "1") +let[@inline] nat_of_ptr (t: ptr) = t +let[@inline] ptr_null = Ligo.nat_from_literal "0n" +let[@inline] ptr_init = Ligo.nat_from_literal "1n" +let[@inline] ptr_next (t: ptr) = Ligo.add_nat_nat t (Ligo.nat_from_literal "1n") (* BEGIN_OCAML *) -let compare_ptr = Common.compare_int -let random_ptr () = Ligo.int_from_int64 (Random.int64 Int64.max_int) +let compare_ptr = Common.compare_nat +let random_ptr () = Ligo.nat_from_int64 (Random.int64 Int64.max_int) (* END_OCAML *) diff --git a/src/ptr.mli b/src/ptr.mli index 9c6b5964..159ca7da 100644 --- a/src/ptr.mli +++ b/src/ptr.mli @@ -1,5 +1,6 @@ type ptr (* George: perhaps I'd prefer a phantom parameter to not mix up pointers. *) +val nat_of_ptr : ptr -> Ligo.nat val ptr_null : ptr val ptr_init : ptr val ptr_next : ptr -> ptr From e256d136e1a7e18d50241d1422fb356fe35c6f0f Mon Sep 17 00:00:00 2001 From: Utku Demir Date: Thu, 17 Jun 2021 22:24:17 +1200 Subject: [PATCH 02/12] Ask for an auction id on 'liquidation_auction_place_bid' endpoint --- src/checker.ml | 6 +++++- src/checker.mli | 3 ++- src/error.ml | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/checker.ml b/src/checker.ml index 4fb156d5..e5f1fa0d 100644 --- a/src/checker.ml +++ b/src/checker.ml @@ -18,6 +18,7 @@ open Error open Fa12Interface open Fa2Interface open Mem +open Ptr (* BEGIN_OCAML *) let assert_checker_invariants (state: checker) : unit = @@ -615,12 +616,15 @@ let entrypoint_remove_liquidity (state, p: checker * (lqt * ctez * kit * Ligo.ti (** LIQUIDATION AUCTIONS *) (* ************************************************************************* *) -let entrypoint_liquidation_auction_place_bid (state, kit: checker * kit) : LigoOp.operation list * checker = +let entrypoint_liquidation_auction_place_bid (state, (auction_id, kit): checker * (Ligo.nat * kit)) : LigoOp.operation list * checker = assert_checker_invariants state; let _ = ensure_no_tez_given () in let bid = { address=(!Ligo.Tezos.sender); kit=kit; } in let current_auction = liquidation_auction_get_current_auction state.liquidation_auctions in + let () = if nat_of_ptr (ptr_of_avl_ptr current_auction.contents) = auction_id + then () + else Ligo.failwith error_InvalidLiquidationAuction in let (new_current_auction, old_winning_bid) = liquidation_auction_place_bid current_auction bid in diff --git a/src/checker.mli b/src/checker.mli index f671b593..55a2a5a1 100644 --- a/src/checker.mli +++ b/src/checker.mli @@ -205,9 +205,10 @@ val entrypoint_remove_liquidity : checker * (lqt * ctez * kit * Ligo.timestamp) auction, or if the bid is too low. Parameters: + - identifier of the current liquidation auction - The amount of kit to be bid *) -val entrypoint_liquidation_auction_place_bid : checker * kit -> LigoOp.operation list * checker +val entrypoint_liquidation_auction_place_bid : checker * (Ligo.nat * kit) -> LigoOp.operation list * checker (** Claim the rewards of a completed liquidation auction. Fails if the sender is not the auction winner, if the auction is still ongoing, or if the diff --git a/src/error.ml b/src/error.ml index d36b5775..47ec68f6 100644 --- a/src/error.ml +++ b/src/error.ml @@ -55,6 +55,7 @@ let[@inline] error_NotACompletedSlice : Ligo.int = let[@inline] error_InvalidAvlPtr : Ligo.int = Ligo.int_from_literal "88" let[@inline] error_InvalidLeafPtr : Ligo.int = Ligo.int_from_literal "89" let[@inline] error_BurrowAlreadyExists : Ligo.int = Ligo.int_from_literal "90" +let[@inline] error_InvalidLiquidationAuction : Ligo.int = Ligo.int_from_literal "91" let[@inline] error_GetContractOptFailure : Ligo.int = Ligo.int_from_literal "95" From 1d7583762203b459c1bd8173ae7ae1d3d5ccb22c Mon Sep 17 00:00:00 2001 From: Utku Demir Date: Thu, 17 Jun 2021 22:24:51 +1200 Subject: [PATCH 03/12] Add 'current_liquidation_auction_minimum_bid' view --- src/checker.ml | 7 +++++++ src/checker.mli | 2 ++ src/checkerTypes.ml | 6 ++++++ 3 files changed, 15 insertions(+) diff --git a/src/checker.ml b/src/checker.ml index e5f1fa0d..ae70cd2c 100644 --- a/src/checker.ml +++ b/src/checker.ml @@ -924,6 +924,13 @@ let view_is_burrow_liquidatable (burrow_id, state: burrow_id * checker) : bool = let burrow = burrow_touch state.parameters burrow in burrow_is_liquidatable state.parameters burrow +let view_current_liquidation_auction_minimum_bid ((), state: unit * checker) : view_current_liquidation_auction_minimum_bid_result = + assert_checker_invariants state; + let auction = liquidation_auction_get_current_auction state.liquidation_auctions in + { auction_id = nat_of_ptr (ptr_of_avl_ptr auction.contents) + ; minimum_bid = liquidation_auction_current_auction_minimum_bid auction + } + (* ************************************************************************* *) (** FA2_VIEWS *) (* ************************************************************************* *) diff --git a/src/checker.mli b/src/checker.mli index 55a2a5a1..44efaca9 100644 --- a/src/checker.mli +++ b/src/checker.mli @@ -258,6 +258,8 @@ val view_add_liquidity_min_lqt_minted : (ctez * checker) -> lqt val view_remove_liquidity_min_ctez_withdrawn : (lqt * checker) -> ctez val view_remove_liquidity_min_kit_withdrawn : (lqt * checker) -> kit +val view_current_liquidation_auction_minimum_bid: (unit * checker) -> view_current_liquidation_auction_minimum_bid_result + val view_burrow_max_mintable_kit : (burrow_id * checker) -> kit val view_is_burrow_overburrowed : (burrow_id * checker) -> bool val view_is_burrow_liquidatable : (burrow_id * checker) -> bool diff --git a/src/checkerTypes.ml b/src/checkerTypes.ml index 840378d2..8058be8b 100644 --- a/src/checkerTypes.ml +++ b/src/checkerTypes.ml @@ -1,3 +1,4 @@ +open Kit open Burrow open CfmmTypes open Parameters @@ -45,3 +46,8 @@ type wrapper = ; metadata: (string, Ligo.bytes) Ligo.big_map ; deployment_state : deployment_state } + +type view_current_liquidation_auction_minimum_bid_result = + { auction_id: Ligo.nat + ; minimum_bid: kit + } From 7853b7b94209c352f274c88cc03d4d44b442b714 Mon Sep 17 00:00:00 2001 From: Utku Demir Date: Thu, 17 Jun 2021 22:25:21 +1200 Subject: [PATCH 04/12] 'LiquidationsStressTest' e2e test case --- e2e/main.py | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 2 deletions(-) diff --git a/e2e/main.py b/e2e/main.py index 37360db5..51fd342b 100644 --- a/e2e/main.py +++ b/e2e/main.py @@ -1,5 +1,6 @@ -import json import os +import json +import time from collections import OrderedDict import unittest from datetime import datetime @@ -12,7 +13,6 @@ from checker_client.checker import * - class SandboxedTestCase(unittest.TestCase): def setUp(self): port = portpicker.pick_unused_port() @@ -156,6 +156,128 @@ def call_checker_endpoint(name, param, amount=0): gas_costs_sorted[k] = v print(json.dumps(gas_costs_sorted, indent=4)) +class LiquidationsStressTest(SandboxedTestCase): + def test_liquidations(self): + print("Deploying the mock oracle.") + oracle = deploy_contract( + self.client, + source_file=os.path.join(PROJECT_ROOT, "util/mock_oracle.tz"), + initial_storage=(self.client.key.public_key_hash(), 1000000), + ttl=MAX_OPERATIONS_TTL, + ) + + print("Deploying ctez contract.") + ctez = deploy_ctez( + self.client, + ctez_dir=os.path.join(PROJECT_ROOT, "vendor/ctez"), + ttl=MAX_OPERATIONS_TTL, + ) + + print("Deploying Checker.") + checker = deploy_checker( + self.client, + checker_dir=os.path.join(PROJECT_ROOT, "generated/michelson"), + oracle=oracle.context.address, + ctez=ctez["fa12_ctez"].context.address, + ttl=MAX_OPERATIONS_TTL, + ) + + print("Deployment finished.") + + + def call_endpoint(contract, name, param, amount=0): + print( + "Calling", contract.key.public_key_hash(), "/", name, + "with", param + ) + return inject( + self.client, + getattr(contract, name)(param) + .with_amount(amount) + .as_transaction() + .autofill(ttl=MAX_OPERATIONS_TTL) + .sign() + ) + + def call_bulk(bulks, *, chunk_size): + chunks = [bulks[i:i+chunk_size] for i in range(0, len(bulks), chunk_size)] + for chunk_no, chunk in enumerate(chunks, 1): + print("Sending", len(bulks), "operations as bulk:", "Chunk", chunk_no, "of", len(chunks)) + inject(self.client, self.client.bulk(*chunk).autofill(ttl=MAX_OPERATIONS_TTL).sign()) + + call_bulk( + [ checker.create_burrow((0, None)).with_amount(200_000_000) + , checker.mint_kit((0, 80_000_000)) + ], + chunk_size=10 + ) + + call_endpoint(ctez["ctez"], "create", (1, None, {"any": None}), amount=2_000_000) + call_endpoint(ctez["ctez"], "mint_or_burn", (1, 100_000)) + call_endpoint(ctez["fa12_ctez"], "approve", (checker.context.address, 100_000)) + + call_endpoint(checker, "add_liquidity", (100_000, 100_000, 5, int(datetime.now().timestamp()) + 20)) + + burrows = list(range(1, 1001)) + + call_bulk( + [ checker.create_burrow((burrow_id, None)).with_amount(100_000_000) + for burrow_id in burrows + ], + chunk_size=100 + ) + + # Mint as much as possible from the burrows. All should be identical, so we just query the + # first burrow and mint that much kit from all of them. + max_mintable_kit = checker.metadata.burrowMaxMintableKit((self.client.key.public_key_hash(), 1)).storage_view() + + bulks = [] + for burrow_no in burrows: + bulks.append(checker.touch_burrow((self.client.key.public_key_hash(), burrow_no))) + bulks.append(checker.mint_kit(burrow_no, max_mintable_kit)) + + call_bulk(bulks, chunk_size=100) + + # Change the index (kits are 100x valuable) + # + # Keep in mind that we're using a patched checker on tests where the protected index + # is much faster to update. + call_endpoint(oracle, "update", 100_000_000) + + # Oracle updates lag one touch on checker + call_endpoint(checker, "touch", None) + call_endpoint(checker, "touch", None) + time.sleep(20) + call_endpoint(checker, "touch", None) + + # Now burrows should be overburrowed, so we liquidate them all. + # + # This should use the push_back method of the AVL tree. + bulks = [] + for burrow_no in burrows: + bid = (self.client.key.public_key_hash(), burrow_no) + bulks.append(checker.touch_burrow(bid)) + bulks.append(checker.mark_for_liquidation(bid)) + + call_bulk(bulks, chunk_size=40) + + # This touch starts a liquidation auction + # + # This would use the split method of the AVL tree. However, if the entire queue + # has less tez than the limit, the 'split' method would trivially return the entire + # tree without much effort; so here we should ensure that the liquidation queue has + # more tez than the limit (FIXME). + call_endpoint(checker, "touch", None) + + # And we place a bid + + ret = checker.metadata.currentLiquidationAuctionMinimumBid().storage_view() + auction_id, minimum_bid = ret["contents"], ret["nat_1"] + # The return value is supposed to be annotated as "auction_id" and "minimum_bid", I + # do not know why we get these names. I think there is an underlying pytezos bug + # that we should reproduce and create a bug upstream. + + call_endpoint(checker, "liquidation_auction_place_bid", (auction_id, minimum_bid)) if __name__ == "__main__": unittest.main() From 9bd9fcc947f0a6c6b017c3bb0848e06f732211d5 Mon Sep 17 00:00:00 2001 From: Utku Demir Date: Fri, 18 Jun 2021 16:59:48 +1200 Subject: [PATCH 05/12] Fix tests --- tests/testChecker.ml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/testChecker.ml b/tests/testChecker.ml index a67bbf93..22c20a7c 100644 --- a/tests/testChecker.ml +++ b/tests/testChecker.ml @@ -895,12 +895,7 @@ let suite = Ligo.Tezos.new_transaction ~seconds_passed:(5*60) ~blocks_passed:5 ~sender:bob_addr ~amount:(Ligo.tez_from_literal "0mutez"); assert_raises (Failure (Ligo.string_of_int error_NoOpenAuction)) - (fun () -> - Checker.entrypoint_liquidation_auction_place_bid - ( checker - , (kit_of_mukit (Ligo.nat_from_literal "1_000n")) - ) - ); + (fun () ->Checker.view_current_liquidation_auction_minimum_bid ((), checker)); let kit_before_reward = get_balance_of checker bob_addr kit_token_id in let _, checker = Checker.touch_with_index checker (Ligo.tez_from_literal "1_200_000mutez") in @@ -922,11 +917,12 @@ let suite = let kit_after_reward = get_balance_of checker alice_addr kit_token_id in let touch_reward = Ligo.sub_nat_nat kit_after_reward kit_before_reward in + let auction_id = (Checker.view_current_liquidation_auction_minimum_bid ((), checker)).auction_id in let (ops, checker) = Checker.entrypoint_liquidation_auction_place_bid ( checker - , (kit_of_mukit (Ligo.nat_from_literal "4_200_000n")) + , (auction_id, kit_of_mukit (Ligo.nat_from_literal "4_200_000n")) ) in let auction_id = From 050831bfb378279ff99aa32b972ae54e735e93a1 Mon Sep 17 00:00:00 2001 From: Utku Demir Date: Fri, 18 Jun 2021 18:25:56 +1200 Subject: [PATCH 06/12] Mechanism to pass a patched checker to e2e stress tests --- default.nix | 7 ++++++- e2e/main.py | 8 ++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/default.nix b/default.nix index 7e6f4912..a50c7a47 100644 --- a/default.nix +++ b/default.nix @@ -1,4 +1,4 @@ -{ doCheck ? false }: +{ doCheck ? false, e2eTestsHack ? false }: let sources = import ./nix/sources.nix { }; @@ -69,6 +69,11 @@ rec name = "checker-michelson"; buildInputs = [ ligoBinary ] ++ (with pkgs; [ ruby ]) ++ ocamlDeps pkgs; src = checkerSource; + patchPhase = pkgs.lib.optional e2eTestsHack '' + set -x + cat ${./patches/e2e-tests-hack.patch} | patch -p1 + set +x + ''; buildPhase = '' export HOME=$(mktemp -d) make build-ligo diff --git a/e2e/main.py b/e2e/main.py index 51fd342b..eaff3749 100644 --- a/e2e/main.py +++ b/e2e/main.py @@ -10,6 +10,10 @@ from pytezos.operation import MAX_OPERATIONS_TTL PROJECT_ROOT = os.path.join(os.path.dirname(__file__), "../") +CHECKER_DIR = os.environ.get( + "CHECKER_DIR", + os.path.join(PROJECT_ROOT, "generated/michelson") +) from checker_client.checker import * @@ -60,7 +64,7 @@ def test_e2e(self): print("Deploying Checker.") checker = deploy_checker( self.client, - checker_dir=os.path.join(PROJECT_ROOT, "generated/michelson"), + checker_dir=CHECKER_DIR, oracle=oracle.context.address, ctez=ctez["fa12_ctez"].context.address, ttl=MAX_OPERATIONS_TTL, @@ -176,7 +180,7 @@ def test_liquidations(self): print("Deploying Checker.") checker = deploy_checker( self.client, - checker_dir=os.path.join(PROJECT_ROOT, "generated/michelson"), + checker_dir=CHECKER_DIR, oracle=oracle.context.address, ctez=ctez["fa12_ctez"].context.address, ttl=MAX_OPERATIONS_TTL, From 4d6796c29c29e22a57146b7e77dafaf96993fb85 Mon Sep 17 00:00:00 2001 From: Utku Demir Date: Fri, 18 Jun 2021 22:40:01 +1200 Subject: [PATCH 07/12] Add missing file --- patches/e2e-tests-hack.patch | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 patches/e2e-tests-hack.patch diff --git a/patches/e2e-tests-hack.patch b/patches/e2e-tests-hack.patch new file mode 100644 index 00000000..73948d9d --- /dev/null +++ b/patches/e2e-tests-hack.patch @@ -0,0 +1,12 @@ +diff --git a/src/parameters.ml b/src/parameters.ml +index 395ecd1..4907c90 100644 +--- a/src/parameters.ml ++++ b/src/parameters.ml +@@ -211,6 +211,7 @@ let[@inline] compute_current_burrow_fee_index (last_burrow_fee_index: fixedpoint + *) + let[@inline] compute_current_protected_index (last_protected_index: Ligo.tez) (current_index: Ligo.tez) (duration_in_seconds: Ligo.int) : Ligo.tez = + assert (Ligo.gt_tez_tez last_protected_index (Ligo.tez_from_literal "0mutez")); ++ let duration_in_seconds = Ligo.mul_int_int duration_in_seconds (Ligo.int_from_literal "1000") in + let last_protected_index = tez_to_mutez last_protected_index in + fraction_to_tez_floor + (clamp_int From 7afdea9a6d99246320194724da8106c6921a6376 Mon Sep 17 00:00:00 2001 From: Utku Demir Date: Fri, 18 Jun 2021 22:47:36 +1200 Subject: [PATCH 08/12] Formatting --- e2e/main.py | 77 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 26 deletions(-) diff --git a/e2e/main.py b/e2e/main.py index eaff3749..c11d94e2 100644 --- a/e2e/main.py +++ b/e2e/main.py @@ -11,12 +11,12 @@ PROJECT_ROOT = os.path.join(os.path.dirname(__file__), "../") CHECKER_DIR = os.environ.get( - "CHECKER_DIR", - os.path.join(PROJECT_ROOT, "generated/michelson") + "CHECKER_DIR", os.path.join(PROJECT_ROOT, "generated/michelson") ) from checker_client.checker import * + class SandboxedTestCase(unittest.TestCase): def setUp(self): port = portpicker.pick_unused_port() @@ -160,6 +160,7 @@ def call_checker_endpoint(name, param, amount=0): gas_costs_sorted[k] = v print(json.dumps(gas_costs_sorted, indent=4)) + class LiquidationsStressTest(SandboxedTestCase): def test_liquidations(self): print("Deploying the mock oracle.") @@ -188,56 +189,77 @@ def test_liquidations(self): print("Deployment finished.") - def call_endpoint(contract, name, param, amount=0): - print( - "Calling", contract.key.public_key_hash(), "/", name, - "with", param - ) + print("Calling", contract.key.public_key_hash(), "/", name, "with", param) return inject( self.client, getattr(contract, name)(param) - .with_amount(amount) - .as_transaction() - .autofill(ttl=MAX_OPERATIONS_TTL) - .sign() + .with_amount(amount) + .as_transaction() + .autofill(ttl=MAX_OPERATIONS_TTL) + .sign(), ) def call_bulk(bulks, *, chunk_size): - chunks = [bulks[i:i+chunk_size] for i in range(0, len(bulks), chunk_size)] + chunks = [ + bulks[i : i + chunk_size] for i in range(0, len(bulks), chunk_size) + ] for chunk_no, chunk in enumerate(chunks, 1): - print("Sending", len(bulks), "operations as bulk:", "Chunk", chunk_no, "of", len(chunks)) - inject(self.client, self.client.bulk(*chunk).autofill(ttl=MAX_OPERATIONS_TTL).sign()) + print( + "Sending", + len(bulks), + "operations as bulk:", + "Chunk", + chunk_no, + "of", + len(chunks), + ) + inject( + self.client, + self.client.bulk(*chunk).autofill(ttl=MAX_OPERATIONS_TTL).sign(), + ) call_bulk( - [ checker.create_burrow((0, None)).with_amount(200_000_000) - , checker.mint_kit((0, 80_000_000)) + [ + checker.create_burrow((0, None)).with_amount(200_000_000), + checker.mint_kit((0, 80_000_000)), ], - chunk_size=10 + chunk_size=10, ) - call_endpoint(ctez["ctez"], "create", (1, None, {"any": None}), amount=2_000_000) + call_endpoint( + ctez["ctez"], "create", (1, None, {"any": None}), amount=2_000_000 + ) call_endpoint(ctez["ctez"], "mint_or_burn", (1, 100_000)) call_endpoint(ctez["fa12_ctez"], "approve", (checker.context.address, 100_000)) - call_endpoint(checker, "add_liquidity", (100_000, 100_000, 5, int(datetime.now().timestamp()) + 20)) + call_endpoint( + checker, + "add_liquidity", + (100_000, 100_000, 5, int(datetime.now().timestamp()) + 20), + ) burrows = list(range(1, 1001)) call_bulk( - [ checker.create_burrow((burrow_id, None)).with_amount(100_000_000) - for burrow_id in burrows - ], - chunk_size=100 + [ + checker.create_burrow((burrow_id, None)).with_amount(100_000_000) + for burrow_id in burrows + ], + chunk_size=100, ) # Mint as much as possible from the burrows. All should be identical, so we just query the # first burrow and mint that much kit from all of them. - max_mintable_kit = checker.metadata.burrowMaxMintableKit((self.client.key.public_key_hash(), 1)).storage_view() + max_mintable_kit = checker.metadata.burrowMaxMintableKit( + (self.client.key.public_key_hash(), 1) + ).storage_view() bulks = [] for burrow_no in burrows: - bulks.append(checker.touch_burrow((self.client.key.public_key_hash(), burrow_no))) + bulks.append( + checker.touch_burrow((self.client.key.public_key_hash(), burrow_no)) + ) bulks.append(checker.mint_kit(burrow_no, max_mintable_kit)) call_bulk(bulks, chunk_size=100) @@ -281,7 +303,10 @@ def call_bulk(bulks, *, chunk_size): # do not know why we get these names. I think there is an underlying pytezos bug # that we should reproduce and create a bug upstream. - call_endpoint(checker, "liquidation_auction_place_bid", (auction_id, minimum_bid)) + call_endpoint( + checker, "liquidation_auction_place_bid", (auction_id, minimum_bid) + ) + if __name__ == "__main__": unittest.main() From 9de8afc4be87854b7b8ff6ddecb43b9c384cb9e3 Mon Sep 17 00:00:00 2001 From: Utku Demir Date: Mon, 21 Jun 2021 18:06:17 +1200 Subject: [PATCH 09/12] Apply review suggestions --- e2e/main.py | 44 +++++++++++++++++++------------------------- src/checker.ml | 7 +++---- src/checker.mli | 2 +- src/checkerTypes.ml | 3 ++- src/ligo.ml | 3 +-- src/ligo.mli | 1 - src/ptr.ml | 1 - src/ptr.mli | 1 - tests/testChecker.ml | 2 +- 9 files changed, 27 insertions(+), 37 deletions(-) diff --git a/e2e/main.py b/e2e/main.py index c11d94e2..05d4ce49 100644 --- a/e2e/main.py +++ b/e2e/main.py @@ -200,23 +200,23 @@ def call_endpoint(contract, name, param, amount=0): .sign(), ) - def call_bulk(bulks, *, chunk_size): - chunks = [ - bulks[i : i + chunk_size] for i in range(0, len(bulks), chunk_size) + def call_bulk(bulks, *, batch_size): + batches = [ + bulks[i : i + batch_size] for i in range(0, len(bulks), batch_size) ] - for chunk_no, chunk in enumerate(chunks, 1): + for batch_no, batch in enumerate(batches, 1): print( "Sending", - len(bulks), + len(batches), "operations as bulk:", - "Chunk", - chunk_no, + "Batch", + batch_no, "of", - len(chunks), + len(batches), ) inject( self.client, - self.client.bulk(*chunk).autofill(ttl=MAX_OPERATIONS_TTL).sign(), + self.client.bulk(*batch).autofill(ttl=MAX_OPERATIONS_TTL).sign(), ) call_bulk( @@ -224,7 +224,7 @@ def call_bulk(bulks, *, chunk_size): checker.create_burrow((0, None)).with_amount(200_000_000), checker.mint_kit((0, 80_000_000)), ], - chunk_size=10, + batch_size=10, ) call_endpoint( @@ -246,7 +246,7 @@ def call_bulk(bulks, *, chunk_size): checker.create_burrow((burrow_id, None)).with_amount(100_000_000) for burrow_id in burrows ], - chunk_size=100, + batch_size=100, ) # Mint as much as possible from the burrows. All should be identical, so we just query the @@ -255,14 +255,11 @@ def call_bulk(bulks, *, chunk_size): (self.client.key.public_key_hash(), 1) ).storage_view() - bulks = [] - for burrow_no in burrows: - bulks.append( - checker.touch_burrow((self.client.key.public_key_hash(), burrow_no)) - ) - bulks.append(checker.mint_kit(burrow_no, max_mintable_kit)) - call_bulk(bulks, chunk_size=100) + call_bulk( + [checker.mint_kit(burrow_no, max_mintable_kit) for burrow_no in burrows], + batch_size=100 + ) # Change the index (kits are 100x valuable) # @@ -279,13 +276,10 @@ def call_bulk(bulks, *, chunk_size): # Now burrows should be overburrowed, so we liquidate them all. # # This should use the push_back method of the AVL tree. - bulks = [] - for burrow_no in burrows: - bid = (self.client.key.public_key_hash(), burrow_no) - bulks.append(checker.touch_burrow(bid)) - bulks.append(checker.mark_for_liquidation(bid)) - - call_bulk(bulks, chunk_size=40) + call_bulk( + [checker.mark_for_liquidation((self.client.key.public_key_hash(), burrow_no)) for burrow_no in burrows], + batch_size=40 + ) # This touch starts a liquidation auction # diff --git a/src/checker.ml b/src/checker.ml index ae70cd2c..82054969 100644 --- a/src/checker.ml +++ b/src/checker.ml @@ -18,7 +18,6 @@ open Error open Fa12Interface open Fa2Interface open Mem -open Ptr (* BEGIN_OCAML *) let assert_checker_invariants (state: checker) : unit = @@ -616,13 +615,13 @@ let entrypoint_remove_liquidity (state, p: checker * (lqt * ctez * kit * Ligo.ti (** LIQUIDATION AUCTIONS *) (* ************************************************************************* *) -let entrypoint_liquidation_auction_place_bid (state, (auction_id, kit): checker * (Ligo.nat * kit)) : LigoOp.operation list * checker = +let entrypoint_liquidation_auction_place_bid (state, (auction_id, kit): checker * (liquidation_auction_id * kit)) : LigoOp.operation list * checker = assert_checker_invariants state; let _ = ensure_no_tez_given () in let bid = { address=(!Ligo.Tezos.sender); kit=kit; } in let current_auction = liquidation_auction_get_current_auction state.liquidation_auctions in - let () = if nat_of_ptr (ptr_of_avl_ptr current_auction.contents) = auction_id + let () = if current_auction.contents = auction_id then () else Ligo.failwith error_InvalidLiquidationAuction in @@ -927,7 +926,7 @@ let view_is_burrow_liquidatable (burrow_id, state: burrow_id * checker) : bool = let view_current_liquidation_auction_minimum_bid ((), state: unit * checker) : view_current_liquidation_auction_minimum_bid_result = assert_checker_invariants state; let auction = liquidation_auction_get_current_auction state.liquidation_auctions in - { auction_id = nat_of_ptr (ptr_of_avl_ptr auction.contents) + { auction_id = auction.contents ; minimum_bid = liquidation_auction_current_auction_minimum_bid auction } diff --git a/src/checker.mli b/src/checker.mli index 44efaca9..a113c230 100644 --- a/src/checker.mli +++ b/src/checker.mli @@ -208,7 +208,7 @@ val entrypoint_remove_liquidity : checker * (lqt * ctez * kit * Ligo.timestamp) - identifier of the current liquidation auction - The amount of kit to be bid *) -val entrypoint_liquidation_auction_place_bid : checker * (Ligo.nat * kit) -> LigoOp.operation list * checker +val entrypoint_liquidation_auction_place_bid : checker * (liquidation_auction_id * kit) -> LigoOp.operation list * checker (** Claim the rewards of a completed liquidation auction. Fails if the sender is not the auction winner, if the auction is still ongoing, or if the diff --git a/src/checkerTypes.ml b/src/checkerTypes.ml index 8058be8b..a98df140 100644 --- a/src/checkerTypes.ml +++ b/src/checkerTypes.ml @@ -3,6 +3,7 @@ open Burrow open CfmmTypes open Parameters open LiquidationAuctionTypes +open LiquidationAuctionPrimitiveTypes open Fa2Interface type burrow_map = (burrow_id, burrow) Ligo.big_map @@ -48,6 +49,6 @@ type wrapper = } type view_current_liquidation_auction_minimum_bid_result = - { auction_id: Ligo.nat + { auction_id: liquidation_auction_id ; minimum_bid: kit } diff --git a/src/ligo.ml b/src/ligo.ml index 4942bd44..ceae9a5a 100644 --- a/src/ligo.ml +++ b/src/ligo.ml @@ -142,10 +142,9 @@ let int_from_literal s = try parse_int_with_suffix "" s with exc -> failwith ("Ligo.int_from_literal: " ^ Printexc.to_string exc) -let int_from_int64 = Z.of_int64 let nat_from_int64 (t: Int64.t) = let r = Z.of_int64 t in - assert (r > Z.zero); + assert (r >= Z.zero); r let add_int_int = Z.add diff --git a/src/ligo.mli b/src/ligo.mli index c047b2a3..7e77ca7f 100644 --- a/src/ligo.mli +++ b/src/ligo.mli @@ -172,7 +172,6 @@ val address_from_literal : String.t -> address (* IN LIGO: type-annotate with "a val key_hash_from_literal : String.t -> key_hash (* IN LIGO: type-annotate with "key_hash". *) val bytes_from_literal : String.t -> bytes (* IN LIGO: replace the double quotes with parens *) -val int_from_int64: Int64.t -> int (* NON-LIGO, temporary*) val nat_from_int64: Int64.t -> nat (* NON-LIGO, temporary*) val timestamp_from_seconds_literal : Int.t -> timestamp (* NON-LIGO: in LIGO they come from strings, or Tezos.now *) diff --git a/src/ptr.ml b/src/ptr.ml index ec5eb61d..a39338b3 100644 --- a/src/ptr.ml +++ b/src/ptr.ml @@ -1,7 +1,6 @@ type ptr = Ligo.nat (* BEGIN_OCAML *) [@@deriving show] (* END_OCAML *) -let[@inline] nat_of_ptr (t: ptr) = t let[@inline] ptr_null = Ligo.nat_from_literal "0n" let[@inline] ptr_init = Ligo.nat_from_literal "1n" let[@inline] ptr_next (t: ptr) = Ligo.add_nat_nat t (Ligo.nat_from_literal "1n") diff --git a/src/ptr.mli b/src/ptr.mli index 159ca7da..9c6b5964 100644 --- a/src/ptr.mli +++ b/src/ptr.mli @@ -1,6 +1,5 @@ type ptr (* George: perhaps I'd prefer a phantom parameter to not mix up pointers. *) -val nat_of_ptr : ptr -> Ligo.nat val ptr_null : ptr val ptr_init : ptr val ptr_next : ptr -> ptr diff --git a/tests/testChecker.ml b/tests/testChecker.ml index 22c20a7c..a8ae3b11 100644 --- a/tests/testChecker.ml +++ b/tests/testChecker.ml @@ -895,7 +895,7 @@ let suite = Ligo.Tezos.new_transaction ~seconds_passed:(5*60) ~blocks_passed:5 ~sender:bob_addr ~amount:(Ligo.tez_from_literal "0mutez"); assert_raises (Failure (Ligo.string_of_int error_NoOpenAuction)) - (fun () ->Checker.view_current_liquidation_auction_minimum_bid ((), checker)); + (fun () -> Checker.view_current_liquidation_auction_minimum_bid ((), checker)); let kit_before_reward = get_balance_of checker bob_addr kit_token_id in let _, checker = Checker.touch_with_index checker (Ligo.tez_from_literal "1_200_000mutez") in From ce5779f74799d3ac018ec8156d105ea449be78c2 Mon Sep 17 00:00:00 2001 From: Utku Demir Date: Mon, 21 Jun 2021 18:15:43 +1200 Subject: [PATCH 10/12] Indent --- e2e/main.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/e2e/main.py b/e2e/main.py index 05d4ce49..27ead449 100644 --- a/e2e/main.py +++ b/e2e/main.py @@ -255,10 +255,9 @@ def call_bulk(bulks, *, batch_size): (self.client.key.public_key_hash(), 1) ).storage_view() - call_bulk( - [checker.mint_kit(burrow_no, max_mintable_kit) for burrow_no in burrows], - batch_size=100 + [checker.mint_kit(burrow_no, max_mintable_kit) for burrow_no in burrows], + batch_size=100, ) # Change the index (kits are 100x valuable) @@ -277,8 +276,13 @@ def call_bulk(bulks, *, batch_size): # # This should use the push_back method of the AVL tree. call_bulk( - [checker.mark_for_liquidation((self.client.key.public_key_hash(), burrow_no)) for burrow_no in burrows], - batch_size=40 + [ + checker.mark_for_liquidation( + (self.client.key.public_key_hash(), burrow_no) + ) + for burrow_no in burrows + ], + batch_size=40, ) # This touch starts a liquidation auction From ce29d0b22395c41000a16ac916892d86ade966a7 Mon Sep 17 00:00:00 2001 From: Utku Demir Date: Mon, 21 Jun 2021 19:41:53 +1200 Subject: [PATCH 11/12] Add a test case for liquidation_auction_place_bid checking the given auction_id --- tests/testChecker.ml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/testChecker.ml b/tests/testChecker.ml index a8ae3b11..45383834 100644 --- a/tests/testChecker.ml +++ b/tests/testChecker.ml @@ -736,6 +736,39 @@ let suite = (fun () -> Checker.entrypoint_update_operators (empty_checker, [])) ); + ("entrypoint_liquidation_auction_place_bid: should only allow the current auction" >:: + fun _ -> + Ligo.Tezos.reset (); + let checker = { empty_checker with last_price = Some (Ligo.nat_from_literal "1_000_000n") } in + + Ligo.Tezos.new_transaction ~seconds_passed:10 ~blocks_passed:1 ~sender:alice_addr ~amount:(Ligo.tez_from_literal "0mutez"); + let _, checker = Checker.entrypoint_touch (checker, ()) in + + Ligo.Tezos.new_transaction ~seconds_passed:10 ~blocks_passed:1 ~sender:alice_addr ~amount:(Ligo.tez_from_literal "200_000_000mutez"); + let _, checker = Checker.entrypoint_create_burrow (checker, (Ligo.nat_from_literal "0n", None)) in + let max_kit = Checker.view_burrow_max_mintable_kit ((alice_addr, Ligo.nat_from_literal "0n"), checker) in + + Ligo.Tezos.new_transaction ~seconds_passed:10 ~blocks_passed:1 ~sender:alice_addr ~amount:(Ligo.tez_from_literal "0mutez"); + let _, checker = Checker.entrypoint_mint_kit (checker, (Ligo.nat_from_literal "0n", max_kit)) in + let checker = { checker with last_price = Some (Ligo.nat_from_literal "10_000_000n") } in + let _, checker = Checker.entrypoint_touch (checker, ()) in + + Ligo.Tezos.new_transaction ~seconds_passed:1_000_000 ~blocks_passed:1 ~sender:alice_addr ~amount:(Ligo.tez_from_literal "0mutez"); + let _, checker = Checker.entrypoint_touch (checker, ()) in + + Ligo.Tezos.new_transaction ~seconds_passed:10 ~blocks_passed:1 ~sender:alice_addr ~amount:(Ligo.tez_from_literal "0mutez"); + let _, checker = Checker.entrypoint_touch_burrow (checker, (alice_addr, Ligo.nat_from_literal "0n")) in + let _, checker = Checker.entrypoint_mark_for_liquidation (checker, (alice_addr, Ligo.nat_from_literal "0n")) in + let _, checker = Checker.entrypoint_touch (checker, ()) in + + let res = Checker.view_current_liquidation_auction_minimum_bid ((), checker) in + let other_ptr = match res.auction_id with AVLPtr i -> Ptr.ptr_next i in + + assert_raises + (Failure (Ligo.string_of_int error_InvalidLiquidationAuction)) + (fun () -> Checker.entrypoint_liquidation_auction_place_bid (checker, (AVLPtr other_ptr, res.minimum_bid))); + ); + ("can complete a liquidation auction" >:: fun _ -> Ligo.Tezos.reset (); From 59bf7e750d046d9764e1bf79b742c1f275b33a3b Mon Sep 17 00:00:00 2001 From: Utku Demir Date: Mon, 21 Jun 2021 19:45:45 +1200 Subject: [PATCH 12/12] nix: Add a comment explaining the 'e2eTestsHack' argument --- default.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/default.nix b/default.nix index a50c7a47..35889623 100644 --- a/default.nix +++ b/default.nix @@ -69,6 +69,9 @@ rec name = "checker-michelson"; buildInputs = [ ligoBinary ] ++ (with pkgs; [ ruby ]) ++ ocamlDeps pkgs; src = checkerSource; + # On E2E tests, we are using a patched version of checker to be able to experiment + # with index changes without having to wait for the protected index to catch up. + # It's a hack, and shouldn't be used for anything else than the tests. patchPhase = pkgs.lib.optional e2eTestsHack '' set -x cat ${./patches/e2e-tests-hack.patch} | patch -p1