From 0689daebbec730daa5475aee1d10112f2dc61e94 Mon Sep 17 00:00:00 2001 From: Britt Cyr Date: Mon, 25 Nov 2024 10:38:22 -0500 Subject: [PATCH] Sync audit formal verification (#294) * Certora's formal verification effort for Manifest (#259) * Certora's formal verification effort for Manifest - Verification rules are in `programs/manifest/src/certora/spec` - Mock for red-black tree in `cvt_db_mock.rs` - Mock for QuoteAtomsPerBaseAtoms in `quantities_certora.rs` - Uses conditional compilation with feature `certora` to plug in the mocks - See the accompanying audit report for additional details * specs: add rules for matching mechanism --------- Co-authored-by: caballa * Fix build * More making certora compile * More fixes * add updated script * Fix verify * Update rbtree * Fix list of rules for violated rules * Remove expected to be violated rules * Cleanup * Update logs * comment on batch update * Processor * quantities * utils * Update github action * Fix lint * Fix unused * Unused * action remove with * remove container * remove container * Fix yml --------- Co-authored-by: nisarg-certora Co-authored-by: caballa --- .github/workflows/ci-certora.yml | 51 + Cargo.lock | 1459 +++-- Cargo.toml | 12 +- Certora_README.md | 118 + .../rule_cancel_order_ask/expected.json | 6 + .../rule_cancel_order_ask.conf | 6 + .../rule_cancel_order_bid/expected.json | 6 + .../rule_cancel_order_bid.conf | 6 + .../expected.json | 6 + .../rule_cancel_order_by_index_ask.conf | 6 + .../expected.json | 6 + .../rule_cancel_order_by_index_bid.conf | 6 + .../expected.json | 6 + ...e_cancel_order_by_index_no_revert_ask.conf | 6 + .../expected.json | 6 + ...e_cancel_order_by_index_no_revert_bid.conf | 6 + .../expected.json | 6 + ...ule_cancel_order_trader_integrity_ask.conf | 6 + .../expected.json | 6 + ...ule_cancel_order_trader_integrity_bid.conf | 6 + .../CI_tests/rule_deposit_base/expected.json | 6 + .../rule_deposit_base/rule_deposit_base.conf | 6 + .../rule_deposit_deposits/expected.json | 6 + .../rule_deposit_deposits.conf | 6 + .../CI_tests/rule_deposit_quote/expected.json | 6 + .../rule_deposit_quote.conf | 6 + .../expected.json | 6 + ..._integrity_of_batch_update_cancel_ask.conf | 6 + .../expected.json | 6 + ..._integrity_of_batch_update_cancel_bid.conf | 6 + .../expected.json | 6 + ...grity_of_batch_update_cancel_hint_ask.conf | 6 + .../expected.json | 6 + ...grity_of_batch_update_cancel_hint_bid.conf | 6 + .../expected.json | 6 + ...grity_of_batch_update_place_order_ask.conf | 6 + .../expected.json | 6 + ...grity_of_batch_update_place_order_bid.conf | 6 + .../rule_market_claim_seat_once/expected.json | 6 + .../rule_market_claim_seat_once.conf | 6 + .../expected.json | 6 + ...ket_claim_seat_twice_different_trader.conf | 6 + .../expected.json | 6 + ...e_market_claim_seat_twice_same_trader.conf | 6 + .../rule_market_deposit/expected.json | 6 + .../rule_market_deposit.conf | 6 + .../CI_tests/rule_market_empty/expected.json | 6 + .../rule_market_empty/rule_market_empty.conf | 6 + .../rule_market_release_seat/expected.json | 6 + .../rule_market_release_seat.conf | 6 + .../expected.json | 6 + .../rule_place_single_order_canceled_ask.conf | 6 + .../expected.json | 6 + .../rule_place_single_order_canceled_bid.conf | 6 + .../expected.json | 6 + ...ule_place_single_order_full_match_ask.conf | 6 + .../expected.json | 6 + ...ule_place_single_order_full_match_bid.conf | 6 + .../expected.json | 6 + ..._place_single_order_partial_match_ask.conf | 6 + .../expected.json | 6 + ..._place_single_order_partial_match_bid.conf | 6 + .../expected.json | 6 + ...rule_place_single_order_unmatched_ask.conf | 6 + .../expected.json | 6 + ...rule_place_single_order_unmatched_bid.conf | 6 + .../rule_rest_remaining_ask/expected.json | 6 + .../rule_rest_remaining_ask.conf | 6 + .../rule_rest_remaining_bid/expected.json | 6 + .../rule_rest_remaining_bid.conf | 6 + .../rule_swap_base_exact/expected.json | 6 + .../rule_swap_base_exact.conf | 6 + .../rule_swap_base_not_exact/expected.json | 6 + .../rule_swap_base_not_exact.conf | 6 + .../rule_swap_quote_exact/expected.json | 6 + .../rule_swap_quote_exact.conf | 6 + .../rule_swap_quote_not_exact/expected.json | 6 + .../rule_swap_quote_not_exact.conf | 6 + .../rule_update_balance/expected.json | 6 + .../rule_update_balance.conf | 6 + .../CI_tests/rule_withdraw_base/expected.json | 6 + .../rule_withdraw_base.conf | 6 + .../expected.json | 6 + .../rule_withdraw_does_not_revert.conf | 6 + .../rule_withdraw_quote/expected.json | 6 + .../rule_withdraw_quote.conf | 6 + .../rule_withdraw_withdraws/expected.json | 6 + .../rule_withdraw_withdraws.conf | 6 + certora/CI_tests/test_ignore.txt | 0 certora/cvt_inlining.txt | 163 + certora/cvt_summaries.txt | 89 + client/rust/Cargo.toml | 2 +- lib/Cargo.toml | 8 + lib/src/hypertree.rs | 45 +- lib/src/red_black_tree.rs | 87 + lib/src/utils.rs | 7 + programs/manifest/Cargo.toml | 13 + programs/manifest/justfile | 135 + programs/manifest/rules-rb-tree.json | 190 + programs/manifest/rules.json | 292 + programs/manifest/src/certora/hooks.rs | 66 + .../src/certora/mocks_batch_update.rs | 33 + programs/manifest/src/certora/mod.rs | 6 + .../src/certora/spec/batch_update_checks.rs | 186 + .../src/certora/spec/cancel_order_checks.rs | 67 + .../src/certora/spec/deposit_checks.rs | 122 + .../manifest/src/certora/spec/funds_checks.rs | 464 ++ .../src/certora/spec/market_checks.rs | 150 + .../src/certora/spec/matching_checks.rs | 474 ++ programs/manifest/src/certora/spec/mod.rs | 31 + .../src/certora/spec/no_funds_loss_util.rs | 624 +++ .../src/certora/spec/place_order_checks.rs | 344 ++ .../src/certora/spec/rbtree_checks.rs | 4724 +++++++++++++++++ .../manifest/src/certora/spec/swap_checks.rs | 150 + .../src/certora/spec/withdraw_checks.rs | 112 + .../certora/summaries/impact_base_atoms.rs | 18 + .../manifest/src/certora/summaries/mod.rs | 2 + .../src/certora/summaries/place_order.rs | 102 + programs/manifest/src/certora/utils.rs | 242 + programs/manifest/src/lib.rs | 3 + programs/manifest/src/logs.rs | 14 +- programs/manifest/src/program/error.rs | 10 + .../batch_update_instruction.rs | 24 + .../src/program/processor/batch_update.rs | 127 +- .../src/program/processor/claim_seat.rs | 5 + .../src/program/processor/create_market.rs | 4 +- .../manifest/src/program/processor/deposit.rs | 175 +- .../src/program/processor/global_clean.rs | 10 +- .../src/program/processor/global_evict.rs | 4 +- .../manifest/src/program/processor/shared.rs | 57 +- .../manifest/src/program/processor/swap.rs | 487 +- .../src/program/processor/withdraw.rs | 189 +- programs/manifest/src/quantities.rs | 45 +- programs/manifest/src/quantities_certora.rs | 106 + programs/manifest/src/state/claimed_seat.rs | 15 + programs/manifest/src/state/cvt_db_mock.rs | 501 ++ programs/manifest/src/state/cvt_munge.rs | 5 + programs/manifest/src/state/global.rs | 35 +- programs/manifest/src/state/market.rs | 550 +- programs/manifest/src/state/market_helpers.rs | 553 ++ programs/manifest/src/state/resting_order.rs | 22 + programs/manifest/src/state/utils.rs | 13 +- programs/manifest/src/validation/loaders.rs | 164 +- programs/manifest/verify-manifest.py | 234 + .../ui-wrapper/tests/cases/place_order.rs | 40 +- programs/wrapper/src/processors/shared.rs | 4 +- 146 files changed, 13515 insertions(+), 973 deletions(-) create mode 100644 .github/workflows/ci-certora.yml create mode 100644 Certora_README.md create mode 100644 certora/CI_tests/rule_cancel_order_ask/expected.json create mode 100644 certora/CI_tests/rule_cancel_order_ask/rule_cancel_order_ask.conf create mode 100644 certora/CI_tests/rule_cancel_order_bid/expected.json create mode 100644 certora/CI_tests/rule_cancel_order_bid/rule_cancel_order_bid.conf create mode 100644 certora/CI_tests/rule_cancel_order_by_index_ask/expected.json create mode 100644 certora/CI_tests/rule_cancel_order_by_index_ask/rule_cancel_order_by_index_ask.conf create mode 100644 certora/CI_tests/rule_cancel_order_by_index_bid/expected.json create mode 100644 certora/CI_tests/rule_cancel_order_by_index_bid/rule_cancel_order_by_index_bid.conf create mode 100644 certora/CI_tests/rule_cancel_order_by_index_no_revert_ask/expected.json create mode 100644 certora/CI_tests/rule_cancel_order_by_index_no_revert_ask/rule_cancel_order_by_index_no_revert_ask.conf create mode 100644 certora/CI_tests/rule_cancel_order_by_index_no_revert_bid/expected.json create mode 100644 certora/CI_tests/rule_cancel_order_by_index_no_revert_bid/rule_cancel_order_by_index_no_revert_bid.conf create mode 100644 certora/CI_tests/rule_cancel_order_trader_integrity_ask/expected.json create mode 100644 certora/CI_tests/rule_cancel_order_trader_integrity_ask/rule_cancel_order_trader_integrity_ask.conf create mode 100644 certora/CI_tests/rule_cancel_order_trader_integrity_bid/expected.json create mode 100644 certora/CI_tests/rule_cancel_order_trader_integrity_bid/rule_cancel_order_trader_integrity_bid.conf create mode 100644 certora/CI_tests/rule_deposit_base/expected.json create mode 100644 certora/CI_tests/rule_deposit_base/rule_deposit_base.conf create mode 100644 certora/CI_tests/rule_deposit_deposits/expected.json create mode 100644 certora/CI_tests/rule_deposit_deposits/rule_deposit_deposits.conf create mode 100644 certora/CI_tests/rule_deposit_quote/expected.json create mode 100644 certora/CI_tests/rule_deposit_quote/rule_deposit_quote.conf create mode 100644 certora/CI_tests/rule_integrity_of_batch_update_cancel_ask/expected.json create mode 100644 certora/CI_tests/rule_integrity_of_batch_update_cancel_ask/rule_integrity_of_batch_update_cancel_ask.conf create mode 100644 certora/CI_tests/rule_integrity_of_batch_update_cancel_bid/expected.json create mode 100644 certora/CI_tests/rule_integrity_of_batch_update_cancel_bid/rule_integrity_of_batch_update_cancel_bid.conf create mode 100644 certora/CI_tests/rule_integrity_of_batch_update_cancel_hint_ask/expected.json create mode 100644 certora/CI_tests/rule_integrity_of_batch_update_cancel_hint_ask/rule_integrity_of_batch_update_cancel_hint_ask.conf create mode 100644 certora/CI_tests/rule_integrity_of_batch_update_cancel_hint_bid/expected.json create mode 100644 certora/CI_tests/rule_integrity_of_batch_update_cancel_hint_bid/rule_integrity_of_batch_update_cancel_hint_bid.conf create mode 100644 certora/CI_tests/rule_integrity_of_batch_update_place_order_ask/expected.json create mode 100644 certora/CI_tests/rule_integrity_of_batch_update_place_order_ask/rule_integrity_of_batch_update_place_order_ask.conf create mode 100644 certora/CI_tests/rule_integrity_of_batch_update_place_order_bid/expected.json create mode 100644 certora/CI_tests/rule_integrity_of_batch_update_place_order_bid/rule_integrity_of_batch_update_place_order_bid.conf create mode 100644 certora/CI_tests/rule_market_claim_seat_once/expected.json create mode 100644 certora/CI_tests/rule_market_claim_seat_once/rule_market_claim_seat_once.conf create mode 100644 certora/CI_tests/rule_market_claim_seat_twice_different_trader/expected.json create mode 100644 certora/CI_tests/rule_market_claim_seat_twice_different_trader/rule_market_claim_seat_twice_different_trader.conf create mode 100644 certora/CI_tests/rule_market_claim_seat_twice_same_trader/expected.json create mode 100644 certora/CI_tests/rule_market_claim_seat_twice_same_trader/rule_market_claim_seat_twice_same_trader.conf create mode 100644 certora/CI_tests/rule_market_deposit/expected.json create mode 100644 certora/CI_tests/rule_market_deposit/rule_market_deposit.conf create mode 100644 certora/CI_tests/rule_market_empty/expected.json create mode 100644 certora/CI_tests/rule_market_empty/rule_market_empty.conf create mode 100644 certora/CI_tests/rule_market_release_seat/expected.json create mode 100644 certora/CI_tests/rule_market_release_seat/rule_market_release_seat.conf create mode 100644 certora/CI_tests/rule_place_single_order_canceled_ask/expected.json create mode 100644 certora/CI_tests/rule_place_single_order_canceled_ask/rule_place_single_order_canceled_ask.conf create mode 100644 certora/CI_tests/rule_place_single_order_canceled_bid/expected.json create mode 100644 certora/CI_tests/rule_place_single_order_canceled_bid/rule_place_single_order_canceled_bid.conf create mode 100644 certora/CI_tests/rule_place_single_order_full_match_ask/expected.json create mode 100644 certora/CI_tests/rule_place_single_order_full_match_ask/rule_place_single_order_full_match_ask.conf create mode 100644 certora/CI_tests/rule_place_single_order_full_match_bid/expected.json create mode 100644 certora/CI_tests/rule_place_single_order_full_match_bid/rule_place_single_order_full_match_bid.conf create mode 100644 certora/CI_tests/rule_place_single_order_partial_match_ask/expected.json create mode 100644 certora/CI_tests/rule_place_single_order_partial_match_ask/rule_place_single_order_partial_match_ask.conf create mode 100644 certora/CI_tests/rule_place_single_order_partial_match_bid/expected.json create mode 100644 certora/CI_tests/rule_place_single_order_partial_match_bid/rule_place_single_order_partial_match_bid.conf create mode 100644 certora/CI_tests/rule_place_single_order_unmatched_ask/expected.json create mode 100644 certora/CI_tests/rule_place_single_order_unmatched_ask/rule_place_single_order_unmatched_ask.conf create mode 100644 certora/CI_tests/rule_place_single_order_unmatched_bid/expected.json create mode 100644 certora/CI_tests/rule_place_single_order_unmatched_bid/rule_place_single_order_unmatched_bid.conf create mode 100644 certora/CI_tests/rule_rest_remaining_ask/expected.json create mode 100644 certora/CI_tests/rule_rest_remaining_ask/rule_rest_remaining_ask.conf create mode 100644 certora/CI_tests/rule_rest_remaining_bid/expected.json create mode 100644 certora/CI_tests/rule_rest_remaining_bid/rule_rest_remaining_bid.conf create mode 100644 certora/CI_tests/rule_swap_base_exact/expected.json create mode 100644 certora/CI_tests/rule_swap_base_exact/rule_swap_base_exact.conf create mode 100644 certora/CI_tests/rule_swap_base_not_exact/expected.json create mode 100644 certora/CI_tests/rule_swap_base_not_exact/rule_swap_base_not_exact.conf create mode 100644 certora/CI_tests/rule_swap_quote_exact/expected.json create mode 100644 certora/CI_tests/rule_swap_quote_exact/rule_swap_quote_exact.conf create mode 100644 certora/CI_tests/rule_swap_quote_not_exact/expected.json create mode 100644 certora/CI_tests/rule_swap_quote_not_exact/rule_swap_quote_not_exact.conf create mode 100644 certora/CI_tests/rule_update_balance/expected.json create mode 100644 certora/CI_tests/rule_update_balance/rule_update_balance.conf create mode 100644 certora/CI_tests/rule_withdraw_base/expected.json create mode 100644 certora/CI_tests/rule_withdraw_base/rule_withdraw_base.conf create mode 100644 certora/CI_tests/rule_withdraw_does_not_revert/expected.json create mode 100644 certora/CI_tests/rule_withdraw_does_not_revert/rule_withdraw_does_not_revert.conf create mode 100644 certora/CI_tests/rule_withdraw_quote/expected.json create mode 100644 certora/CI_tests/rule_withdraw_quote/rule_withdraw_quote.conf create mode 100644 certora/CI_tests/rule_withdraw_withdraws/expected.json create mode 100644 certora/CI_tests/rule_withdraw_withdraws/rule_withdraw_withdraws.conf create mode 100644 certora/CI_tests/test_ignore.txt create mode 100644 certora/cvt_inlining.txt create mode 100644 certora/cvt_summaries.txt create mode 100644 programs/manifest/justfile create mode 100644 programs/manifest/rules-rb-tree.json create mode 100644 programs/manifest/rules.json create mode 100644 programs/manifest/src/certora/hooks.rs create mode 100644 programs/manifest/src/certora/mocks_batch_update.rs create mode 100644 programs/manifest/src/certora/mod.rs create mode 100644 programs/manifest/src/certora/spec/batch_update_checks.rs create mode 100644 programs/manifest/src/certora/spec/cancel_order_checks.rs create mode 100644 programs/manifest/src/certora/spec/deposit_checks.rs create mode 100644 programs/manifest/src/certora/spec/funds_checks.rs create mode 100644 programs/manifest/src/certora/spec/market_checks.rs create mode 100644 programs/manifest/src/certora/spec/matching_checks.rs create mode 100644 programs/manifest/src/certora/spec/mod.rs create mode 100644 programs/manifest/src/certora/spec/no_funds_loss_util.rs create mode 100644 programs/manifest/src/certora/spec/place_order_checks.rs create mode 100644 programs/manifest/src/certora/spec/rbtree_checks.rs create mode 100644 programs/manifest/src/certora/spec/swap_checks.rs create mode 100644 programs/manifest/src/certora/spec/withdraw_checks.rs create mode 100644 programs/manifest/src/certora/summaries/impact_base_atoms.rs create mode 100644 programs/manifest/src/certora/summaries/mod.rs create mode 100644 programs/manifest/src/certora/summaries/place_order.rs create mode 100644 programs/manifest/src/certora/utils.rs create mode 100644 programs/manifest/src/quantities_certora.rs create mode 100644 programs/manifest/src/state/cvt_db_mock.rs create mode 100644 programs/manifest/src/state/cvt_munge.rs create mode 100644 programs/manifest/src/state/market_helpers.rs create mode 100755 programs/manifest/verify-manifest.py diff --git a/.github/workflows/ci-certora.yml b/.github/workflows/ci-certora.yml new file mode 100644 index 000000000..425bfd003 --- /dev/null +++ b/.github/workflows/ci-certora.yml @@ -0,0 +1,51 @@ +name: Certora Formal Verification +on: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + +env: + SOLANA_VERSION: '1.18.16' + +jobs: + verification: + name: Formal Verification + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup python + uses: actions/setup-python@v5 + + - name: Install certora CLI + run: pip install certora-cli + + - name: Install solana + run: | + sh -c "$(curl -sSfL https://release.solana.com/v${{ env.SOLANA_VERSION }}/install)" + export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH" + + - name: Install certora platform tools + run: | + mkdir $HOME/platform-tools-certora + wget https://github.com/Certora/certora-solana-platform-tools/releases/download/linux-x86_64-ubuntu-20.04-10-22-2024/platform-tools-linux-x86_64.tar.bz2 + cd $HOME + tar -xvjf platform-tools-linux-x86_64.tar.bz2 -C $HOME/platform-tools-certora + export PLATFORM_TOOLS_DIR=$HOME/.cache/solana/v1.41 + mv $PLATFORM_TOOLS_DIR/platform-tools $PLATFORM_TOOLS_DIR/platform-tools.backup + ln -sf $HOME/platform-tools-certora $PLATFORM_TOOLS_DIR/platform-tools + + - name: Install just + uses: extractions/setup-just@v2 + + - name: Run formal verification + run: | + cd programs/manifest + just build-sbf + python3 verify-manifest.py -r rules.json + python3 verify-manifest.py -r rules-rb-tree.json + env: + CERTORA: "" + CERTORA_CLI: certoraRun + CERTORAKEY: ${{ secrets.CERTORAKEY }} diff --git a/Cargo.lock b/Cargo.lock index 28d29feee..530d40a54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,19 +14,13 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" @@ -123,6 +117,154 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "anchor-attribute-access-control" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f619f1d04f53621925ba8a2e633ba5a6081f2ae14758cbb67f38fd823e0a3e" +dependencies = [ + "anchor-syn", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-account" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f2a3e1df4685f18d12a943a9f2a7456305401af21a07c9fe076ef9ecd6e400" +dependencies = [ + "anchor-syn", + "bs58 0.5.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-constant" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9423945cb55627f0b30903288e78baf6f62c6c8ab28fb344b6b25f1ffee3dca7" +dependencies = [ + "anchor-syn", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-error" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ed12720033cc3c3bf3cfa293349c2275cd5ab99936e33dd4bf283aaad3e241" +dependencies = [ + "anchor-syn", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-event" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef4dc0371eba2d8c8b54794b0b0eb786a234a559b77593d6f80825b6d2c77a2" +dependencies = [ + "anchor-syn", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-program" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b18c4f191331e078d4a6a080954d1576241c29c56638783322a18d308ab27e4f" +dependencies = [ + "anchor-syn", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-accounts" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de10d6e9620d3bcea56c56151cad83c5992f50d5960b3a9bebc4a50390ddc3c" +dependencies = [ + "anchor-syn", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-serde" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e2e5be518ec6053d90a2a7f26843dbee607583c779e6c8395951b9739bdfbe" +dependencies = [ + "anchor-syn", + "borsh-derive-internal 0.10.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-space" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecc31d19fa54840e74b7a979d44bcea49d70459de846088a1d71e87ba53c419" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-lang" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35da4785497388af0553586d55ebdc08054a8b1724720ef2749d313494f2b8ad" +dependencies = [ + "anchor-attribute-access-control", + "anchor-attribute-account", + "anchor-attribute-constant", + "anchor-attribute-error", + "anchor-attribute-event", + "anchor-attribute-program", + "anchor-derive-accounts", + "anchor-derive-serde", + "anchor-derive-space", + "arrayref", + "base64 0.13.1", + "bincode", + "borsh 0.10.4", + "bytemuck", + "getrandom 0.2.15", + "solana-program", + "thiserror", +] + +[[package]] +name = "anchor-syn" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9101b84702fed2ea57bd22992f75065da5648017135b844283a2f6d74f27825" +dependencies = [ + "anyhow", + "bs58 0.5.1", + "heck 0.3.3", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2 0.10.8", + "syn 1.0.109", + "thiserror", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -149,9 +291,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "aquamarine" @@ -286,9 +428,9 @@ dependencies = [ [[package]] name = "arrayref" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -327,7 +469,7 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "synstructure", + "synstructure 0.12.6", ] [[package]] @@ -360,9 +502,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.12" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" +checksum = "0cb8f1d480b0ea3783ab015936d2a55c87e219676f0c0b7dec61494043f21857" dependencies = [ "brotli", "flate2", @@ -383,13 +525,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -405,23 +547,23 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide 0.7.4", + "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -448,6 +590,15 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "basic-toml" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +dependencies = [ + "serde", +] + [[package]] name = "bincode" version = "1.3.3" @@ -544,21 +695,21 @@ dependencies = [ [[package]] name = "borsh" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" +checksum = "115e54d64eb62cdebad391c19efc9dce4981c690c85a33a12199d99bb9546fee" dependencies = [ - "borsh-derive 0.10.3", + "borsh-derive 0.10.4", "hashbrown 0.13.2", ] [[package]] name = "borsh" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03" dependencies = [ - "borsh-derive 1.5.1", + "borsh-derive 1.5.3", "cfg_aliases", ] @@ -577,12 +728,12 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" +checksum = "831213f80d9423998dd696e2c5345aba6be7a0bd8cd19e31c5243e13df1cef89" dependencies = [ - "borsh-derive-internal 0.10.3", - "borsh-schema-derive-internal 0.10.3", + "borsh-derive-internal 0.10.4", + "borsh-schema-derive-internal 0.10.4", "proc-macro-crate 0.1.5", "proc-macro2", "syn 1.0.109", @@ -590,16 +741,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244" dependencies = [ "once_cell", - "proc-macro-crate 3.1.0", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.75", - "syn_derive", + "syn 2.0.87", ] [[package]] @@ -615,9 +765,9 @@ dependencies = [ [[package]] name = "borsh-derive-internal" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" +checksum = "65d6ba50644c98714aa2a70d13d7df3cd75cd2b523a2b452bf010443800976b3" dependencies = [ "proc-macro2", "quote", @@ -637,9 +787,9 @@ dependencies = [ [[package]] name = "borsh-schema-derive-internal" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" +checksum = "276691d96f063427be83e6692b86148e488ebba9f48f77788724ca027ba3b6d4" dependencies = [ "proc-macro2", "quote", @@ -648,9 +798,9 @@ dependencies = [ [[package]] name = "brotli" -version = "6.0.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -673,6 +823,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -713,22 +872,22 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.17.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" +checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -739,9 +898,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "bzip2" @@ -764,6 +923,14 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "calltrace" +version = "0.0.1" +source = "git+https://github.com/Certora/solana-cvt.git#c0290ed89a156c9096364ad6e11564bd63f05ea8" +dependencies = [ + "cvt 0.2.0 (git+https://github.com/Certora/solana-cvt.git)", +] + [[package]] name = "caps" version = "0.5.5" @@ -776,9 +943,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.13" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ "jobserver", "libc", @@ -841,7 +1008,7 @@ dependencies = [ "bitflags 1.3.2", "strsim 0.8.0", "textwrap 0.11.0", - "unicode-width", + "unicode-width 0.1.14", "vec_map", ] @@ -911,7 +1078,7 @@ dependencies = [ "encode_unicode", "lazy_static", "libc", - "unicode-width", + "unicode-width 0.1.14", "windows-sys 0.52.0", ] @@ -943,9 +1110,9 @@ checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" [[package]] name = "constant_time_eq" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "core-foundation" @@ -965,9 +1132,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" dependencies = [ "libc", ] @@ -1064,6 +1231,32 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cvt" +version = "0.2.0" +source = "git+https://github.com/Certora/solana-cvt.git?branch=dev-vector-borsh#223829ba1b3bca62cb47b6d829d421f41fe56353" +dependencies = [ + "stubs 0.1.0 (git+https://github.com/Certora/solana-cvt.git?branch=dev-vector-borsh)", +] + +[[package]] +name = "cvt" +version = "0.2.0" +source = "git+https://github.com/Certora/solana-cvt.git#c0290ed89a156c9096364ad6e11564bd63f05ea8" +dependencies = [ + "stubs 0.1.0 (git+https://github.com/Certora/solana-cvt.git)", +] + +[[package]] +name = "cvt-macros" +version = "0.0.1" +source = "git+https://github.com/Certora/solana-cvt.git#c0290ed89a156c9096364ad6e11564bd63f05ea8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "darling" version = "0.20.10" @@ -1085,7 +1278,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -1096,7 +1289,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -1180,6 +1373,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "difflib" version = "0.4.0" @@ -1223,7 +1422,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -1246,7 +1445,7 @@ checksum = "a6cbae11b3de8fce2a456e8ea3dada226b35fe791f0dc1d360c0941f0bb681f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -1261,6 +1460,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abe71d579d1812060163dff96056261deb5bf6729b100fa2e36a68b9649ba3d3" +[[package]] +name = "early-panic" +version = "0.0.1" +source = "git+https://github.com/Certora/solana-cvt.git#c0290ed89a156c9096364ad6e11564bd63f05ea8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "ed25519" version = "1.5.3" @@ -1322,9 +1531,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -1346,7 +1555,7 @@ checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -1359,7 +1568,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -1399,9 +1608,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fastrand" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "feature-probe" @@ -1411,9 +1620,9 @@ checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" [[package]] name = "filetime" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf401df4a4e3872c4fe8151134cf483738e74b67fc934d6532c882b3d24a4550" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", @@ -1423,12 +1632,12 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.32" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c0596c1eac1f9e04ed902702e9878208b336edc9d6fddc8a48387349bab3666" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", - "miniz_oxide 0.8.0", + "miniz_oxide", ] [[package]] @@ -1469,9 +1678,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1484,9 +1693,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1494,15 +1703,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1511,38 +1720,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -1605,9 +1814,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "glob" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "goblin" @@ -1632,10 +1847,10 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.4.0", + "indexmap 2.6.0", "slab", "tokio", - "tokio-util 0.7.11", + "tokio-util 0.7.12", "tracing", ] @@ -1681,6 +1896,21 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "heck" version = "0.4.1" @@ -1738,6 +1968,17 @@ dependencies = [ "hmac 0.8.1", ] +[[package]] +name = "hook_macro" +version = "0.1.0" +source = "git+https://github.com/Certora/solana-cvt.git#c0290ed89a156c9096364ad6e11564bd63f05ea8" +dependencies = [ + "macrotest", + "quote", + "syn 1.0.109", + "trybuild", +] + [[package]] name = "http" version = "0.2.12" @@ -1762,9 +2003,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -1780,9 +2021,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.30" +version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ "bytes", "futures-channel", @@ -1821,16 +2062,19 @@ name = "hypertree" version = "0.1.0" dependencies = [ "bytemuck", + "calltrace", "colored", + "cvt 0.2.0 (git+https://github.com/Certora/solana-cvt.git)", + "nondet 0.4.0 (git+https://github.com/Certora/solana-cvt.git)", "solana-program", "static_assertions", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1849,6 +2093,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1856,13 +2218,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] -name = "idna" -version = "0.5.0" +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "icu_normalizer", + "icu_properties", ] [[package]] @@ -1918,41 +2291,32 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.1", ] [[package]] name = "indicatif" -version = "0.17.8" +version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281" dependencies = [ "console", - "instant", "number_prefix", "portable-atomic", - "unicode-width", -] - -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", + "unicode-width 0.2.0", + "web-time", ] [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "itertools" @@ -1965,9 +2329,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "7a73e9fe3c49d7afb2ace819fa181a287ce54a0983eda4e0eb05c22f82ffe534" [[package]] name = "jobserver" @@ -1980,9 +2344,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -2009,7 +2373,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be954b20150b666b2bc328d1e490294a1c096e545f985d852f53c941209b2649" dependencies = [ "anyhow", - "borsh 0.10.3", + "borsh 0.10.4", "rust_decimal", "serde", "serde_json", @@ -2034,9 +2398,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "libredox" @@ -2115,6 +2479,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + [[package]] name = "lock_api" version = "0.4.12" @@ -2142,32 +2512,54 @@ dependencies = [ [[package]] name = "lz4" -version = "1.26.0" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958b4caa893816eea05507c20cfe47574a43d9a697138a7872990bba8a0ece68" +checksum = "4d1febb2b4a79ddd1980eede06a8f7902197960aa0383ffcfdd62fe723036725" dependencies = [ - "libc", "lz4-sys", ] [[package]] name = "lz4-sys" -version = "1.10.0" +version = "1.11.1+lz4-1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109de74d5d2353660401699a4174a4ff23fcc649caf553df71933c7fb45ad868" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" dependencies = [ "cc", "libc", ] +[[package]] +name = "macrotest" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e2035deb453578ff1cd2da2761ac78abbffffd1d06a0f59261c082ea713fdad" +dependencies = [ + "basic-toml", + "diff", + "glob", + "prettyplease", + "serde", + "serde_derive", + "serde_json", + "syn 2.0.87", +] + [[package]] name = "manifest" version = "0.1.4" dependencies = [ "anyhow", - "borsh 0.9.3", + "arrayref", + "borsh 0.10.4", "bytemuck", + "calltrace", + "cvt 0.2.0 (git+https://github.com/Certora/solana-cvt.git)", + "cvt-macros", + "early-panic", + "hook_macro", "hypertree", + "nondet 0.4.0 (git+https://github.com/Certora/solana-cvt.git)", "num_enum 0.5.11", "shank", "solana-invoke", @@ -2176,11 +2568,13 @@ dependencies = [ "solana-program-test", "solana-sdk", "solana-security-txt", + "solana_cvt", "spl-token 3.5.0", "spl-token-2022 3.0.4", "static_assertions", "thiserror", "tokio", + "vectors", ] [[package]] @@ -2255,15 +2649,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.8.0" @@ -2356,6 +2741,26 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nondet" +version = "0.4.0" +source = "git+https://github.com/Certora/solana-cvt.git?branch=dev-vector-borsh#223829ba1b3bca62cb47b6d829d421f41fe56353" +dependencies = [ + "cvt 0.2.0 (git+https://github.com/Certora/solana-cvt.git?branch=dev-vector-borsh)", + "solana-program", + "stubs 0.1.0 (git+https://github.com/Certora/solana-cvt.git?branch=dev-vector-borsh)", +] + +[[package]] +name = "nondet" +version = "0.4.0" +source = "git+https://github.com/Certora/solana-cvt.git#c0290ed89a156c9096364ad6e11564bd63f05ea8" +dependencies = [ + "cvt 0.2.0 (git+https://github.com/Certora/solana-cvt.git)", + "solana-program", + "stubs 0.1.0 (git+https://github.com/Certora/solana-cvt.git)", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -2432,7 +2837,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -2534,7 +2939,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -2543,10 +2948,10 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 3.1.0", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -2557,9 +2962,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.36.3" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] @@ -2575,9 +2980,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opaque-debug" @@ -2712,29 +3117,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -2755,9 +3160,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "plain" @@ -2779,9 +3184,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "powerfmt" @@ -2828,13 +3233,23 @@ dependencies = [ "termtree", ] +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn 2.0.87", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" dependencies = [ - "toml", + "toml 0.5.11", ] [[package]] @@ -2849,11 +3264,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.21.1", + "toml_edit 0.22.22", ] [[package]] @@ -2882,9 +3297,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -2926,7 +3341,7 @@ checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -2979,9 +3394,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -3106,18 +3521,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -3127,9 +3542,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -3138,9 +3553,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rend" @@ -3184,7 +3599,7 @@ dependencies = [ "system-configuration", "tokio", "tokio-rustls", - "tokio-util 0.7.11", + "tokio-util 0.7.12", "tower-service", "url", "wasm-bindgen", @@ -3281,7 +3696,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" dependencies = [ "arrayvec", - "borsh 1.5.1", + "borsh 1.5.3", "bytes", "num-traits", "rand 0.8.5", @@ -3304,9 +3719,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] @@ -3322,9 +3737,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", "errno", @@ -3378,9 +3793,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "ryu" @@ -3399,11 +3814,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3429,7 +3844,7 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -3463,9 +3878,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", @@ -3488,9 +3903,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.208" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] @@ -3506,20 +3921,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", @@ -3527,6 +3942,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3558,7 +3982,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -3702,9 +4126,9 @@ checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" [[package]] name = "simdutf8" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "siphasher" @@ -3749,14 +4173,14 @@ dependencies = [ [[package]] name = "solana-account-decoder" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4185d569c062983fc2a618ae4ee6fe1a139b36bce7a25045647c49bf0020a53" +checksum = "b109fd3a106e079005167e5b0e6f6d2c88bbedec32530837b584791a8b5abf36" dependencies = [ "Inflector", "base64 0.21.7", "bincode", - "bs58", + "bs58 0.4.0", "bv", "lazy_static", "serde", @@ -3774,9 +4198,9 @@ dependencies = [ [[package]] name = "solana-accounts-db" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c156ddd42a2746e14fe267f85a2f802567dfa7c1702836b0ce69ea3be15a3c3" +checksum = "ec9829d10d521f3ed5e50c12d2b62784e2901aa484a92c2aa3924151da046139" dependencies = [ "arrayref", "bincode", @@ -3835,9 +4259,9 @@ dependencies = [ [[package]] name = "solana-address-lookup-table-program" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f5967c234aa8281f36999ded250403ddacb77863e2a1e157a3203884a13cfa" +checksum = "f3527a26138b5deb126f13c27743f3d95ac533abee5979e4113f6d59ef919cc6" dependencies = [ "bincode", "bytemuck", @@ -3856,11 +4280,11 @@ dependencies = [ [[package]] name = "solana-banks-client" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78204433cdb1945ef3622905f806423f5536cc91205dc8e325efe521394d3ca" +checksum = "e58fa66e1e240097665e7f87b267aa8e976ea3fcbd86918c8fd218c875395ada" dependencies = [ - "borsh 1.5.1", + "borsh 1.5.3", "futures", "solana-banks-interface", "solana-program", @@ -3873,9 +4297,9 @@ dependencies = [ [[package]] name = "solana-banks-interface" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f959539e11afaa554c0ae445bb3c726ad658aa33d8b577b76ab7e0ad6e313405" +checksum = "f54d0a4334c153eadaa0326296a47a92d110c1cc975075fd6e1a7b67067f9812" dependencies = [ "serde", "solana-sdk", @@ -3884,9 +4308,9 @@ dependencies = [ [[package]] name = "solana-banks-server" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5224477dc90857c98bec8ff746926facf525e0216fdfbde51e28d92d5b11b236" +checksum = "8cbe287a0f859362de9b155fabd44e479eba26d5d80e07a7d021297b7b06ecba" dependencies = [ "bincode", "crossbeam-channel", @@ -3904,9 +4328,9 @@ dependencies = [ [[package]] name = "solana-bpf-loader-program" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfab3aa028e4feac760f28e7fb24760813d451e7cff5a13584509ddab4a94311" +checksum = "a8cc27ceda9a22804d73902f5d718ff1331aa53990c2665c90535f6b182db259" dependencies = [ "bincode", "byteorder", @@ -3923,9 +4347,9 @@ dependencies = [ [[package]] name = "solana-bucket-map" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06f781213cf76d8840e688d52fbc3876ae8522d2ac594c1c11ab9b982d7f0336" +checksum = "ca55ec9b8d01d2e3bba9fad77b27c9a8fd51fe12475549b93a853d921b653139" dependencies = [ "bv", "bytemuck", @@ -3941,9 +4365,9 @@ dependencies = [ [[package]] name = "solana-clap-utils" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c817832e71886dbea877d1aa911c9ce2e984a39081bb56ee30d4c835567827a6" +checksum = "074ef478856a45d5627270fbc6b331f91de9aae7128242d9e423931013fb8a2a" dependencies = [ "chrono", "clap 2.34.0", @@ -3958,16 +4382,16 @@ dependencies = [ [[package]] name = "solana-client" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fa9cc6e8e59adf70acbf5cac21342ae8b5e41cbf05519fe5f6287e84ab40f63" +checksum = "24a9f32c42402c4b9484d5868ac74b7e0a746e3905d8bfd756e1203e50cbb87e" dependencies = [ "async-trait", "bincode", "dashmap", "futures", "futures-util", - "indexmap 2.4.0", + "indexmap 2.6.0", "indicatif", "log", "quinn", @@ -3991,9 +4415,9 @@ dependencies = [ [[package]] name = "solana-compute-budget-program" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b176bad40620d1c443365daf24e19fbfccafe8daff60eb3ddd6cbd9cf0fbec58" +checksum = "6af050a6e0b402e322aa21f5441c7e27cdd52624a2d659f455b68afd7cda218c" dependencies = [ "solana-program-runtime", "solana-sdk", @@ -4001,9 +4425,9 @@ dependencies = [ [[package]] name = "solana-config-program" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d02fb29934427f1487d2149fe8bcb405306729b2f22a2ad616bb8ffd024cee7b" +checksum = "9d75b803860c0098e021a26f0624129007c15badd5b0bc2fbd9f0e1a73060d3b" dependencies = [ "bincode", "chrono", @@ -4015,15 +4439,15 @@ dependencies = [ [[package]] name = "solana-connection-cache" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e5a2e26448b3e04ce673794994ff27f3972ec8a806c224eccc02e09f751ca5" +checksum = "b9306ede13e8ceeab8a096bcf5fa7126731e44c201ca1721ea3c38d89bcd4111" dependencies = [ "async-trait", "bincode", "crossbeam-channel", "futures-util", - "indexmap 2.4.0", + "indexmap 2.6.0", "log", "rand 0.8.5", "rayon", @@ -4037,9 +4461,9 @@ dependencies = [ [[package]] name = "solana-cost-model" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4564996ef9f2983efeedb14a38315fa606d3d2cc0a2c8d899c507c5893fe79" +checksum = "c852790063f7646a1c5199234cc82e1304b55a3b3fb8055a0b5c8b0393565c1c" dependencies = [ "lazy_static", "log", @@ -4061,12 +4485,12 @@ dependencies = [ [[package]] name = "solana-frozen-abi" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20a6ef2db80dceb124b7bf81cca3300804bf427d2711973fc3df450ed7dfb26d" +checksum = "03ab2c30c15311b511c0d1151e4ab6bc9a3e080a37e7c6e7c2d96f5784cf9434" dependencies = [ "block-buffer 0.10.4", - "bs58", + "bs58 0.4.0", "bv", "either", "generic-array", @@ -4086,14 +4510,14 @@ dependencies = [ [[package]] name = "solana-frozen-abi-macro" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70088de7d4067d19a7455609e2b393e6086bd847bb39c4d2bf234fc14827ef9e" +checksum = "c142f779c3633ac83c84d04ff06c70e1f558c876f13358bed77ba629c7417932" dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -4108,9 +4532,9 @@ dependencies = [ [[package]] name = "solana-loader-v4-program" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fde1ab49eb031882f4803bf5a8008dca84356717e120ba9276d229ff24633c" +checksum = "78b58f70f5883b0f26a6011ed23f76c493a3f22df63aec46cfe8e1b9bf82b5cc" dependencies = [ "log", "solana-measure", @@ -4121,9 +4545,9 @@ dependencies = [ [[package]] name = "solana-logger" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b129da15193f26db62d62ae6bb9f72361f361bcdc36054be3ab8bc04cc7a4f31" +checksum = "121d36ffb3c6b958763312cbc697fbccba46ee837d3a0aa4fc0e90fcb3b884f3" dependencies = [ "env_logger", "lazy_static", @@ -4132,9 +4556,9 @@ dependencies = [ [[package]] name = "solana-measure" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d195b73093a4964ba6b5943418054a5fcbba23eafdd0842fd973fcceac1a967" +checksum = "5c01a7f9cdc9d9d37a3d5651b2fe7ec9d433c2a3470b9f35897e373b421f0737" dependencies = [ "log", "solana-sdk", @@ -4142,9 +4566,9 @@ dependencies = [ [[package]] name = "solana-metrics" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7b06860ffbf4cf4714182e1b7eb00eb3ff0bcc9cff615d05e01e488923883c" +checksum = "71e36052aff6be1536bdf6f737c6e69aca9dbb6a2f3f582e14ecb0ddc0cd66ce" dependencies = [ "crossbeam-channel", "gethostname", @@ -4157,9 +4581,9 @@ dependencies = [ [[package]] name = "solana-net-utils" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9400b50b8439868a99b5fa2d961d74e37b7a6c1d5865759d0b1c906c2ad6b2a9" +checksum = "2a1f5c6be9c5b272866673741e1ebc64b2ea2118e5c6301babbce526fdfb15f4" dependencies = [ "bincode", "clap 3.2.25", @@ -4195,9 +4619,9 @@ dependencies = [ [[package]] name = "solana-perf" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b01a386e852df67031195094628851b8d239dd71fe17b721c3993277e68cb3ab" +checksum = "28acaf22477566a0fbddd67249ea5d859b39bacdb624aff3fadd3c5745e2643c" dependencies = [ "ahash 0.8.11", "bincode", @@ -4224,9 +4648,9 @@ dependencies = [ [[package]] name = "solana-program" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb2b2c8babfae4cace1a25b6efa00418f3acd852cf55d7cecc0360d3c5050479" +checksum = "c10f4588cefd716b24a1a40dd32c278e43a560ab8ce4de6b5805c9d113afdfa1" dependencies = [ "ark-bn254", "ark-ec", @@ -4236,10 +4660,10 @@ dependencies = [ "bincode", "bitflags 2.6.0", "blake3", - "borsh 0.10.3", + "borsh 0.10.4", "borsh 0.9.3", - "borsh 1.5.1", - "bs58", + "borsh 1.5.3", + "bs58 0.4.0", "bv", "bytemuck", "cc", @@ -4279,9 +4703,9 @@ dependencies = [ [[package]] name = "solana-program-runtime" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0444f9440f4459d377c41470b2eb48b527def81f3052b7a121f6aa8c7350cc52" +checksum = "fbf0c3eab2a80f514289af1f422c121defb030937643c43b117959d6f1932fb5" dependencies = [ "base64 0.21.7", "bincode", @@ -4307,9 +4731,9 @@ dependencies = [ [[package]] name = "solana-program-test" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c76274336971f37dbbd3508aaaa4c98ca0061abd31fb309ad1c6ad132f0c6c0e" +checksum = "c1382a5768ff738e283770ee331d0a4fa04aa1aceed8eb820a97094c93d53b72" dependencies = [ "assert_matches", "async-trait", @@ -4337,9 +4761,9 @@ dependencies = [ [[package]] name = "solana-pubsub-client" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee4a39e41e789b6f100c97d9f40c1d08381bf6e3d0e351065e542091cddb039" +checksum = "b064e76909d33821b80fdd826e6757251934a52958220c92639f634bea90366d" dependencies = [ "crossbeam-channel", "futures-util", @@ -4362,9 +4786,9 @@ dependencies = [ [[package]] name = "solana-quic-client" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baad755c76ee0aab8890f0ef873e61b8b3012c523d33bfa5b062fe9be8cef370" +checksum = "5a90e40ee593f6e9ddd722d296df56743514ae804975a76d47e7afed4e3da244" dependencies = [ "async-mutex", "async-trait", @@ -4389,9 +4813,9 @@ dependencies = [ [[package]] name = "solana-rayon-threadlimit" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c2a0ccb0be7ca79e8ff0d7c786bce586433a5687ffbea522453d0b41c4bf4a" +checksum = "66468f9c014992167de10cc68aad6ac8919a8c8ff428dc88c0d2b4da8c02b8b7" dependencies = [ "lazy_static", "num_cpus", @@ -4399,9 +4823,9 @@ dependencies = [ [[package]] name = "solana-remote-wallet" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d042a812537e3507e1c163c7573fc04c96e12d3eba512e3fe74c7393229fa39" +checksum = "c191019f4d4f84281a6d0dd9a43181146b33019627fc394e42e08ade8976b431" dependencies = [ "console", "dialoguer", @@ -4418,14 +4842,14 @@ dependencies = [ [[package]] name = "solana-rpc-client" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6f5560283bd0a6833d1bd816299785058a870fff51b0df399fdb3ce92c8484" +checksum = "36ed4628e338077c195ddbf790693d410123d17dec0a319b5accb4aaee3fb15c" dependencies = [ "async-trait", "base64 0.21.7", "bincode", - "bs58", + "bs58 0.4.0", "indicatif", "log", "reqwest", @@ -4444,12 +4868,12 @@ dependencies = [ [[package]] name = "solana-rpc-client-api" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e4ca77f89caa9071acadb1eed19c28a6691fd63d0563ed927c96bf734cf1c9c" +checksum = "83c913551faa4a1ae4bbfef6af19f3a5cf847285c05b4409e37c8993b3444229" dependencies = [ "base64 0.21.7", - "bs58", + "bs58 0.4.0", "jsonrpc-core", "reqwest", "semver", @@ -4466,9 +4890,9 @@ dependencies = [ [[package]] name = "solana-rpc-client-nonce-utils" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a6ea9ad81d63f18fb8b3a9b39643cc43eaf909199d67037e724562301d1df7" +checksum = "1a47b6bb1834e6141a799db62bbdcf80d17a7d58d7bc1684c614e01a7293d7cf" dependencies = [ "clap 2.34.0", "solana-clap-utils", @@ -4479,9 +4903,9 @@ dependencies = [ [[package]] name = "solana-runtime" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b50b29b6f5938d2c9b151e9187d4687ca9c26be2c6ebe53ba34826283441" +checksum = "73a12e1270121e1ca6a4e86d6d0f5c339f0811a8435161d9eee54cbb0a083859" dependencies = [ "aquamarine", "arrayref", @@ -4556,16 +4980,16 @@ dependencies = [ [[package]] name = "solana-sdk" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e0f0def5c5af07f53d321cea7b104487b522cfff77c3cae3da361bfe956e9e" +checksum = "580ad66c2f7a4c3cb3244fe21440546bd500f5ecb955ad9826e92a78dded8009" dependencies = [ "assert_matches", "base64 0.21.7", "bincode", "bitflags 2.6.0", - "borsh 1.5.1", - "bs58", + "borsh 1.5.3", + "bs58 0.4.0", "bytemuck", "byteorder", "chrono", @@ -4611,15 +5035,15 @@ dependencies = [ [[package]] name = "solana-sdk-macro" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55c196c8050834c391a34b58e3c9fd86b15452ef1feeeafa1dbeb9d2291dfec" +checksum = "1b75d0f193a27719257af19144fdaebec0415d1c9e9226ae4bd29b791be5e9bd" dependencies = [ - "bs58", + "bs58 0.4.0", "proc-macro2", "quote", "rustversion", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -4630,9 +5054,9 @@ checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183" [[package]] name = "solana-send-transaction-service" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c5fc9df712efd671a5a5b68e58a448dc13b70f59ef16bdd0e8d644813eb67a" +checksum = "3218f670f582126a3859c4fd152e922b93b3748a636bb143f970391925723577" dependencies = [ "crossbeam-channel", "log", @@ -4646,9 +5070,9 @@ dependencies = [ [[package]] name = "solana-stake-program" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624f6d0c84c19a17bf882259303e99e1ed2562a0316c989f847a067aa99d4940" +checksum = "eeb3e0d2dc7080b9fa61b34699b176911684f5e04e8df4b565b2b6c962bb4321" dependencies = [ "bincode", "log", @@ -4661,16 +5085,16 @@ dependencies = [ [[package]] name = "solana-streamer" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "749720d82c5f31f7ec326da1e0baac098201de70f0874719172a55309433b449" +checksum = "f8476e41ad94fe492e8c06697ee35912cf3080aae0c9e9ac6430835256ccf056" dependencies = [ "async-channel", "bytes", "crossbeam-channel", "futures-util", "histogram", - "indexmap 2.4.0", + "indexmap 2.6.0", "itertools", "libc", "log", @@ -4694,9 +5118,9 @@ dependencies = [ [[package]] name = "solana-system-program" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a449f40a516a8e83dcc2ce07643bb3feec4da690f170d438849af06c503cc28" +checksum = "26f31e04f5baad7cbc2281fea312c4e48277da42a93a0ba050b74edc5a74d63c" dependencies = [ "bincode", "log", @@ -4708,9 +5132,9 @@ dependencies = [ [[package]] name = "solana-thin-client" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84535de1253afb6ccc4ae6852eb013ca734c439a902ec5e4684b90ed649a37c2" +checksum = "d8c02245d0d232430e79dc0d624aa42d50006097c3aec99ac82ac299eaa3a73f" dependencies = [ "bincode", "log", @@ -4723,14 +5147,14 @@ dependencies = [ [[package]] name = "solana-tpu-client" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff514462bb715aaea9bc5c0ee60f83ab3f91e04279337c6b07d054153b616dc" +checksum = "67251506ed03de15f1347b46636b45c47da6be75015b4a13f0620b21beb00566" dependencies = [ "async-trait", "bincode", "futures-util", - "indexmap 2.4.0", + "indexmap 2.6.0", "indicatif", "log", "rayon", @@ -4747,15 +5171,15 @@ dependencies = [ [[package]] name = "solana-transaction-status" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670e387049812d42bdc8fcc4ff75452ff3cb00657af979a90f55f6d37dba9dd9" +checksum = "2d3d36db1b2ab2801afd5482aad9fb15ed7959f774c81a77299fdd0ddcf839d4" dependencies = [ "Inflector", "base64 0.21.7", "bincode", - "borsh 0.10.3", - "bs58", + "borsh 0.10.4", + "bs58 0.4.0", "lazy_static", "log", "serde", @@ -4772,9 +5196,9 @@ dependencies = [ [[package]] name = "solana-udp-client" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11183dae826f942ebd0401712c8a52367a4a6312f1cd325f304cd9551226fc8b" +checksum = "3a754a3c2265eb02e0c35aeaca96643951f03cee6b376afe12e0cf8860ffccd1" dependencies = [ "async-trait", "solana-connection-cache", @@ -4787,9 +5211,9 @@ dependencies = [ [[package]] name = "solana-version" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d518e61ce22c812df23d9c61ab9bcbef4df3e3d3dcaa74a999625f11bcf07" +checksum = "f44776bd685cc02e67ba264384acc12ef2931d01d1a9f851cb8cdbd3ce455b9e" dependencies = [ "log", "rustc_version", @@ -4803,9 +5227,9 @@ dependencies = [ [[package]] name = "solana-vote" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae2a4908ac4df02a4adb78f09fe938b31c75f42ba64401b8ac88193eb446943" +checksum = "b5983370c95b615dc5f5d0e85414c499f05380393c578749bcd14c114c77c9bc" dependencies = [ "crossbeam-channel", "itertools", @@ -4822,9 +5246,9 @@ dependencies = [ [[package]] name = "solana-vote-program" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5743503143fb2259c41a973a78e9aeeb8e21f1b03543c3bb85449926ea692719" +checksum = "25810970c91feb579bd3f67dca215fce971522e42bfd59696af89c5dfebd997c" dependencies = [ "bincode", "log", @@ -4844,9 +5268,9 @@ dependencies = [ [[package]] name = "solana-zk-token-proof-program" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5813dc267bea898ff40d3bd662a0a7659170dd19ae5e7c46e8dc0a414a205868" +checksum = "1be1c15d4aace575e2de73ebeb9b37bac455e89bee9a8c3531f47ac5066b33e1" dependencies = [ "bytemuck", "num-derive 0.4.2", @@ -4858,9 +5282,9 @@ dependencies = [ [[package]] name = "solana-zk-token-sdk" -version = "1.18.22" +version = "1.18.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee07fa523b4cfcff68de774db7aa87d2da2c4357155a90bacd9a0a0af70a99" +checksum = "7cbdf4249b6dfcbba7d84e2b53313698043f60f8e22ce48286e6fbe8a17c8d16" dependencies = [ "aes-gcm-siv", "base64 0.21.7", @@ -4885,6 +5309,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "solana_cvt" +version = "0.2.0" +source = "git+https://github.com/Certora/solana-cvt.git#c0290ed89a156c9096364ad6e11564bd63f05ea8" +dependencies = [ + "arrayref", + "cvt 0.2.0 (git+https://github.com/Certora/solana-cvt.git)", + "nondet 0.4.0 (git+https://github.com/Certora/solana-cvt.git)", + "solana-program", +] + [[package]] name = "solana_rbpf" version = "0.8.3" @@ -4933,7 +5368,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "992d9c64c2564cc8f63a4b508bf3ebcdf2254b0429b13cd1d31adb6162432a5f" dependencies = [ "assert_matches", - "borsh 0.10.3", + "borsh 0.10.4", "num-derive 0.4.2", "num-traits", "solana-program", @@ -4972,7 +5407,7 @@ checksum = "07fd7858fc4ff8fb0e34090e41d7eb06a823e1057945c26d480bfc21d2338a93" dependencies = [ "quote", "spl-discriminator-syn 0.1.2", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -4983,7 +5418,7 @@ checksum = "d9e8418ea6269dcfb01c712f0444d2c75542c04448b480e87de59d2865edc750" dependencies = [ "quote", "spl-discriminator-syn 0.2.0", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -4995,7 +5430,7 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.75", + "syn 2.0.87", "thiserror", ] @@ -5008,7 +5443,7 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.75", + "syn 2.0.87", "thiserror", ] @@ -5027,7 +5462,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2881dddfca792737c0706fa0175345ab282b1b0879c7d877bad129645737c079" dependencies = [ - "borsh 0.10.3", + "borsh 0.10.4", "bytemuck", "solana-program", "solana-zk-token-sdk", @@ -5040,7 +5475,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c52d84c55efeef8edcc226743dc089d7e3888b8e3474569aa3eff152b37b9996" dependencies = [ - "borsh 1.5.1", + "borsh 1.5.3", "bytemuck", "solana-program", "solana-zk-token-sdk", @@ -5082,7 +5517,7 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -5094,7 +5529,7 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -5235,7 +5670,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c16ce3ba6979645fb7627aa1e435576172dd63088dc7848cb09aa331fa1fe4f" dependencies = [ - "borsh 0.10.3", + "borsh 0.10.4", "solana-program", "spl-discriminator 0.1.0", "spl-pod 0.1.0", @@ -5249,7 +5684,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3da00495b602ebcf5d8ba8b3ecff1ee454ce4c125c9077747be49c2d62335ba" dependencies = [ - "borsh 1.5.1", + "borsh 1.5.3", "solana-program", "spl-discriminator 0.2.5", "spl-pod 0.2.5", @@ -5315,6 +5750,12 @@ dependencies = [ "spl-program-error 0.4.4", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -5354,13 +5795,29 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", "syn 1.0.109", ] +[[package]] +name = "stubs" +version = "0.1.0" +source = "git+https://github.com/Certora/solana-cvt.git?branch=dev-vector-borsh#223829ba1b3bca62cb47b6d829d421f41fe56353" +dependencies = [ + "solana-program", +] + +[[package]] +name = "stubs" +version = "0.1.0" +source = "git+https://github.com/Certora/solana-cvt.git#c0290ed89a156c9096364ad6e11564bd63f05ea8" +dependencies = [ + "solana-program", +] + [[package]] name = "subtle" version = "2.4.1" @@ -5386,27 +5843,15 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.75" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "syn_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.75", -] - [[package]] name = "sync_wrapper" version = "0.1.2" @@ -5425,6 +5870,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -5454,15 +5910,21 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.41" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" +checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" dependencies = [ "filetime", "libc", "xattr", ] +[[package]] +name = "target-triple" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078" + [[package]] name = "tarpc" version = "0.29.0" @@ -5500,9 +5962,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", @@ -5544,7 +6006,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -5555,7 +6017,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", "test-case-core", ] @@ -5565,7 +6027,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -5576,22 +6038,22 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -5654,6 +6116,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -5671,9 +6143,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.3" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", @@ -5695,7 +6167,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -5726,9 +6198,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -5767,9 +6239,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -5787,11 +6259,26 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.22", +] + [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -5799,20 +6286,22 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.6.0", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.6.0", + "serde", + "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.20", ] [[package]] @@ -5841,7 +6330,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -5884,6 +6373,21 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "trybuild" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dcd332a5496c026f1e14b7f3d2b7bd98e509660c04239c58b0ba38a12daded4" +dependencies = [ + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.8.19", +] + [[package]] name = "tungstenite" version = "0.20.1" @@ -5916,7 +6420,7 @@ name = "ui-wrapper" version = "0.1.4" dependencies = [ "anyhow", - "borsh 0.9.3", + "borsh 0.10.4", "bytemuck", "hypertree", "manifest", @@ -5935,38 +6439,44 @@ dependencies = [ "tokio", ] -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" @@ -6011,9 +6521,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.2" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" dependencies = [ "form_urlencoded", "idna", @@ -6026,11 +6536,23 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" [[package]] name = "valuable" @@ -6044,6 +6566,17 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "vectors" +version = "0.1.0" +source = "git+https://github.com/Certora/solana-cvt.git?branch=dev-vector-borsh#223829ba1b3bca62cb47b6d829d421f41fe56353" +dependencies = [ + "anchor-lang", + "borsh 0.10.4", + "cvt 0.2.0 (git+https://github.com/Certora/solana-cvt.git?branch=dev-vector-borsh)", + "nondet 0.4.0 (git+https://github.com/Certora/solana-cvt.git?branch=dev-vector-borsh)", +] + [[package]] name = "version_check" version = "0.9.5" @@ -6089,9 +6622,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -6100,24 +6633,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -6127,9 +6660,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6137,28 +6670,38 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", @@ -6376,6 +6919,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" @@ -6391,7 +6943,7 @@ name = "wrapper" version = "0.1.4" dependencies = [ "anyhow", - "borsh 0.9.3", + "borsh 0.10.4", "bytemuck", "hypertree", "manifest", @@ -6410,6 +6962,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "wyz" version = "0.5.1" @@ -6457,6 +7021,30 @@ dependencies = [ "time", ] +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure 0.13.1", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -6475,7 +7063,28 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure 0.13.1", ] [[package]] @@ -6495,7 +7104,29 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3f4f568cc..faa659b34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ shank = "0.4.2" spl-token = { version = "=3.5.0", features = ["no-entrypoint"] } spl-token-2022 = { version = "3.0.4", features = [ "no-entrypoint" ] } solana-program = "1.18.1" -borsh = "=0.9.3" +borsh = "=0.10" bytemuck = "1.7.2" num_enum = "=0.5.11" thiserror = "1.0.38" @@ -27,6 +27,16 @@ solana-logger = "1.16.7" solana-sdk = "1.16.7" tokio = "1.28.0" +nondet = { git = "https://github.com/Certora/solana-cvt.git" } +cvt = { git = "https://github.com/Certora/solana-cvt.git" } +early-panic = { git = "https://github.com/Certora/solana-cvt.git" } +cvt-macros = { git = "https://github.com/Certora/solana-cvt.git" } +calltrace = { git = "https://github.com/Certora/solana-cvt.git" } +solana_cvt = { git = "https://github.com/Certora/solana-cvt.git" } +hook_macro = { git = "https://github.com/Certora/solana-cvt.git" } +vectors = { git = "https://github.com/Certora/solana-cvt.git", branch="dev-vector-borsh" } +arrayref = "0.3.7" + # https://doc.rust-lang.org/cargo/reference/profiles.html [profile.release] codegen-units = 1 diff --git a/Certora_README.md b/Certora_README.md new file mode 100644 index 000000000..935cd29d2 --- /dev/null +++ b/Certora_README.md @@ -0,0 +1,118 @@ +# Requirements for compilation from Rust to SBF ## + +1. Instal Certora CLI + +``` +pip install certora-cli +``` + +2. Solana CLI: 1.18.16 + +``` +sh -c "$(curl -sSfL https://release.solana.com/v1.18.16/install)" +``` + +3. Install Certora version of platform-tools 1.41 + + Go to https://github.com/Certora/certora-solana-platform-tools?tab=readme-ov-file#installation-of-executables and follow the instructions. + +4. Install `just` https://github.com/casey/just + + +# Build Solana prover from sources (only available for Certora employees) # + +1. Install rustfilt to demangle Rust symbol names + +```shell +cargo install rustfilt +``` + +2. Download https://github.com/Certora/EVMVerifier +3. Switch to branch `jorge/solana-jsm` +4. Follow installation instructions from here https://github.com/Certora/EVMVerifier?tab=readme-ov-file#installation + +# Generate SBF file # + +1. `cd programs/manifest` +2. `just build-sbf` + +# How to run the prover # + +## Configuration Parameters for Just ## + +Just is controlled by environment variables. These are used to provide location for `certoraRun`, the key for the prover, etc. The easiest way to maintain them is to place them in a file called `.env` somewhere in the ancestor of the `justfile`. This can be at the root of the project, or even in the parent directory shared accross multiple projects. + +A typical `.env` file looks like this: +``` +$ cat .env +CERTORA=[LOCATION OF emv.jar] +CERTORA_CLI=certoraRun +CERTORAKEY=[MYKEY] +``` + +Environment variables can also be used to pass extra options to various build scripts. This is usually only necessary in advanced scenarios. + +## Run locally (only available for Certora employees) ## + +You need to follow the steps from "Build Solana prover from sources". +Then, type: + +1. `cd programs/manifest` +2. `just verify RULE_NAME EXTRA_PROVER_OPTS` + +where `RULE_NAME` must be a public Rust function using `#[rule]`, and +`EXTRA_PROVER_OPTS` follows syntax of options passed to the jar +file. For instance, options such as `-bmc 3 -assumeUnwindCond ` that +tells the prover to unroll all loops up to 3 without adding the +"unwinding" assertion. + +To verify all the rules locally and check that they return the expected result, +run the `verify-manifest` script located in `programs/manifest`: + +``` +cd programs/manifest +./verify-manifest -r rules.json +./verify-manifest -r rules-rb-tree.json +``` +Running `verify-manifest` requires `python3` `>= 3.13` + +## Run remotely ## + +1. `cd programs/manifest` +2. `just verify-remote RULE_NAME EXTRA_PROVER_OPTS` + +where `EXTRA_PROVER_OPTS` follows syntax of options passed to +`CertoraRun`. + +After typing the above command, you should see something like this: + +``` +Connecting to server... +Job submitted to server +Follow your job at https://prover.certora.com +Once the job is completed, the results will be available at https://prover.certora.com/output/26873/37ce3f42dbd9419b942c693c7921652d?anonymousKey=b02ea230da2cf7b5d2681d86361744227668170d +``` + +If you open that above link then you will see the result of running +the Certora prover. + +**VERY IMPORTANT**: both commands `just verify` and `just +verify-remote` will compile the Rust code each time before calling the +Solana prover (i.e., it calls the command `build-sbf`) + + +## Running locally vs remotely ## + +Be aware that `just verify` calls directly the jar file while `just +verify-remote` calls the script `certoraRun`. Therefore, the option +names can vary. For instance, + +```shell +just verify RULE_NAME -bmc 3 -assumeUnwindCond +``` + +and + +```shell +just verify-remote RULE_NAME --loop_iter 3 --optimistic_loop +``` diff --git a/certora/CI_tests/rule_cancel_order_ask/expected.json b/certora/CI_tests/rule_cancel_order_ask/expected.json new file mode 100644 index 000000000..62f2c70c5 --- /dev/null +++ b/certora/CI_tests/rule_cancel_order_ask/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_cancel_order_ask": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_cancel_order_ask/rule_cancel_order_ask.conf b/certora/CI_tests/rule_cancel_order_ask/rule_cancel_order_ask.conf new file mode 100644 index 000000000..110bfbde8 --- /dev/null +++ b/certora/CI_tests/rule_cancel_order_ask/rule_cancel_order_ask.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_cancel_order_ask"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_cancel_order_bid/expected.json b/certora/CI_tests/rule_cancel_order_bid/expected.json new file mode 100644 index 000000000..404d4452c --- /dev/null +++ b/certora/CI_tests/rule_cancel_order_bid/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_cancel_order_bid": "FAIL" + } +} diff --git a/certora/CI_tests/rule_cancel_order_bid/rule_cancel_order_bid.conf b/certora/CI_tests/rule_cancel_order_bid/rule_cancel_order_bid.conf new file mode 100644 index 000000000..bf068d83f --- /dev/null +++ b/certora/CI_tests/rule_cancel_order_bid/rule_cancel_order_bid.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_cancel_order_bid"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_cancel_order_by_index_ask/expected.json b/certora/CI_tests/rule_cancel_order_by_index_ask/expected.json new file mode 100644 index 000000000..5b07eab88 --- /dev/null +++ b/certora/CI_tests/rule_cancel_order_by_index_ask/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_cancel_order_by_index_ask": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_cancel_order_by_index_ask/rule_cancel_order_by_index_ask.conf b/certora/CI_tests/rule_cancel_order_by_index_ask/rule_cancel_order_by_index_ask.conf new file mode 100644 index 000000000..a702c2bdf --- /dev/null +++ b/certora/CI_tests/rule_cancel_order_by_index_ask/rule_cancel_order_by_index_ask.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_cancel_order_by_index_ask"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_cancel_order_by_index_bid/expected.json b/certora/CI_tests/rule_cancel_order_by_index_bid/expected.json new file mode 100644 index 000000000..a31919d5d --- /dev/null +++ b/certora/CI_tests/rule_cancel_order_by_index_bid/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_cancel_order_by_index_bid": "FAIL" + } +} diff --git a/certora/CI_tests/rule_cancel_order_by_index_bid/rule_cancel_order_by_index_bid.conf b/certora/CI_tests/rule_cancel_order_by_index_bid/rule_cancel_order_by_index_bid.conf new file mode 100644 index 000000000..3d078e14f --- /dev/null +++ b/certora/CI_tests/rule_cancel_order_by_index_bid/rule_cancel_order_by_index_bid.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_cancel_order_by_index_bid"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_cancel_order_by_index_no_revert_ask/expected.json b/certora/CI_tests/rule_cancel_order_by_index_no_revert_ask/expected.json new file mode 100644 index 000000000..fc2f92906 --- /dev/null +++ b/certora/CI_tests/rule_cancel_order_by_index_no_revert_ask/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_cancel_order_by_index_no_revert_ask": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_cancel_order_by_index_no_revert_ask/rule_cancel_order_by_index_no_revert_ask.conf b/certora/CI_tests/rule_cancel_order_by_index_no_revert_ask/rule_cancel_order_by_index_no_revert_ask.conf new file mode 100644 index 000000000..ef730a403 --- /dev/null +++ b/certora/CI_tests/rule_cancel_order_by_index_no_revert_ask/rule_cancel_order_by_index_no_revert_ask.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_cancel_order_by_index_no_revert_ask"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_cancel_order_by_index_no_revert_bid/expected.json b/certora/CI_tests/rule_cancel_order_by_index_no_revert_bid/expected.json new file mode 100644 index 000000000..c12f25b8d --- /dev/null +++ b/certora/CI_tests/rule_cancel_order_by_index_no_revert_bid/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_cancel_order_by_index_no_revert_bid": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_cancel_order_by_index_no_revert_bid/rule_cancel_order_by_index_no_revert_bid.conf b/certora/CI_tests/rule_cancel_order_by_index_no_revert_bid/rule_cancel_order_by_index_no_revert_bid.conf new file mode 100644 index 000000000..7eaeb358a --- /dev/null +++ b/certora/CI_tests/rule_cancel_order_by_index_no_revert_bid/rule_cancel_order_by_index_no_revert_bid.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_cancel_order_by_index_no_revert_bid"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_cancel_order_trader_integrity_ask/expected.json b/certora/CI_tests/rule_cancel_order_trader_integrity_ask/expected.json new file mode 100644 index 000000000..6af84dfee --- /dev/null +++ b/certora/CI_tests/rule_cancel_order_trader_integrity_ask/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_cancel_order_trader_integrity_ask": "FAIL" + } +} diff --git a/certora/CI_tests/rule_cancel_order_trader_integrity_ask/rule_cancel_order_trader_integrity_ask.conf b/certora/CI_tests/rule_cancel_order_trader_integrity_ask/rule_cancel_order_trader_integrity_ask.conf new file mode 100644 index 000000000..41099b555 --- /dev/null +++ b/certora/CI_tests/rule_cancel_order_trader_integrity_ask/rule_cancel_order_trader_integrity_ask.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_cancel_order_trader_integrity_ask"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_cancel_order_trader_integrity_bid/expected.json b/certora/CI_tests/rule_cancel_order_trader_integrity_bid/expected.json new file mode 100644 index 000000000..e17e90156 --- /dev/null +++ b/certora/CI_tests/rule_cancel_order_trader_integrity_bid/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_cancel_order_trader_integrity_bid": "FAIL" + } +} diff --git a/certora/CI_tests/rule_cancel_order_trader_integrity_bid/rule_cancel_order_trader_integrity_bid.conf b/certora/CI_tests/rule_cancel_order_trader_integrity_bid/rule_cancel_order_trader_integrity_bid.conf new file mode 100644 index 000000000..7e8e76560 --- /dev/null +++ b/certora/CI_tests/rule_cancel_order_trader_integrity_bid/rule_cancel_order_trader_integrity_bid.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_cancel_order_trader_integrity_bid"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_deposit_base/expected.json b/certora/CI_tests/rule_deposit_base/expected.json new file mode 100644 index 000000000..d7d213fd2 --- /dev/null +++ b/certora/CI_tests/rule_deposit_base/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_deposit_base": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_deposit_base/rule_deposit_base.conf b/certora/CI_tests/rule_deposit_base/rule_deposit_base.conf new file mode 100644 index 000000000..e67135c92 --- /dev/null +++ b/certora/CI_tests/rule_deposit_base/rule_deposit_base.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_deposit_base"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_deposit_deposits/expected.json b/certora/CI_tests/rule_deposit_deposits/expected.json new file mode 100644 index 000000000..a30341344 --- /dev/null +++ b/certora/CI_tests/rule_deposit_deposits/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_deposit_deposits": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_deposit_deposits/rule_deposit_deposits.conf b/certora/CI_tests/rule_deposit_deposits/rule_deposit_deposits.conf new file mode 100644 index 000000000..9b2a88c20 --- /dev/null +++ b/certora/CI_tests/rule_deposit_deposits/rule_deposit_deposits.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_deposit_deposits"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_deposit_quote/expected.json b/certora/CI_tests/rule_deposit_quote/expected.json new file mode 100644 index 000000000..7747b3e59 --- /dev/null +++ b/certora/CI_tests/rule_deposit_quote/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_deposit_quote": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_deposit_quote/rule_deposit_quote.conf b/certora/CI_tests/rule_deposit_quote/rule_deposit_quote.conf new file mode 100644 index 000000000..c62244a2e --- /dev/null +++ b/certora/CI_tests/rule_deposit_quote/rule_deposit_quote.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_deposit_quote"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_integrity_of_batch_update_cancel_ask/expected.json b/certora/CI_tests/rule_integrity_of_batch_update_cancel_ask/expected.json new file mode 100644 index 000000000..ddafecc99 --- /dev/null +++ b/certora/CI_tests/rule_integrity_of_batch_update_cancel_ask/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_integrity_of_batch_update_cancel_ask": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_integrity_of_batch_update_cancel_ask/rule_integrity_of_batch_update_cancel_ask.conf b/certora/CI_tests/rule_integrity_of_batch_update_cancel_ask/rule_integrity_of_batch_update_cancel_ask.conf new file mode 100644 index 000000000..9b1484df9 --- /dev/null +++ b/certora/CI_tests/rule_integrity_of_batch_update_cancel_ask/rule_integrity_of_batch_update_cancel_ask.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_integrity_of_batch_update_cancel_ask"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_integrity_of_batch_update_cancel_bid/expected.json b/certora/CI_tests/rule_integrity_of_batch_update_cancel_bid/expected.json new file mode 100644 index 000000000..77359a841 --- /dev/null +++ b/certora/CI_tests/rule_integrity_of_batch_update_cancel_bid/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_integrity_of_batch_update_cancel_bid": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_integrity_of_batch_update_cancel_bid/rule_integrity_of_batch_update_cancel_bid.conf b/certora/CI_tests/rule_integrity_of_batch_update_cancel_bid/rule_integrity_of_batch_update_cancel_bid.conf new file mode 100644 index 000000000..3e1f68fa5 --- /dev/null +++ b/certora/CI_tests/rule_integrity_of_batch_update_cancel_bid/rule_integrity_of_batch_update_cancel_bid.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_integrity_of_batch_update_cancel_bid"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_integrity_of_batch_update_cancel_hint_ask/expected.json b/certora/CI_tests/rule_integrity_of_batch_update_cancel_hint_ask/expected.json new file mode 100644 index 000000000..db9a86bcf --- /dev/null +++ b/certora/CI_tests/rule_integrity_of_batch_update_cancel_hint_ask/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_integrity_of_batch_update_cancel_hint_ask": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_integrity_of_batch_update_cancel_hint_ask/rule_integrity_of_batch_update_cancel_hint_ask.conf b/certora/CI_tests/rule_integrity_of_batch_update_cancel_hint_ask/rule_integrity_of_batch_update_cancel_hint_ask.conf new file mode 100644 index 000000000..78b62cf39 --- /dev/null +++ b/certora/CI_tests/rule_integrity_of_batch_update_cancel_hint_ask/rule_integrity_of_batch_update_cancel_hint_ask.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_integrity_of_batch_update_cancel_hint_ask"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_integrity_of_batch_update_cancel_hint_bid/expected.json b/certora/CI_tests/rule_integrity_of_batch_update_cancel_hint_bid/expected.json new file mode 100644 index 000000000..5f007c114 --- /dev/null +++ b/certora/CI_tests/rule_integrity_of_batch_update_cancel_hint_bid/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_integrity_of_batch_update_cancel_hint_bid": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_integrity_of_batch_update_cancel_hint_bid/rule_integrity_of_batch_update_cancel_hint_bid.conf b/certora/CI_tests/rule_integrity_of_batch_update_cancel_hint_bid/rule_integrity_of_batch_update_cancel_hint_bid.conf new file mode 100644 index 000000000..83db47066 --- /dev/null +++ b/certora/CI_tests/rule_integrity_of_batch_update_cancel_hint_bid/rule_integrity_of_batch_update_cancel_hint_bid.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_integrity_of_batch_update_cancel_hint_bid"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_integrity_of_batch_update_place_order_ask/expected.json b/certora/CI_tests/rule_integrity_of_batch_update_place_order_ask/expected.json new file mode 100644 index 000000000..13c62a507 --- /dev/null +++ b/certora/CI_tests/rule_integrity_of_batch_update_place_order_ask/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_integrity_of_batch_update_place_order_ask": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_integrity_of_batch_update_place_order_ask/rule_integrity_of_batch_update_place_order_ask.conf b/certora/CI_tests/rule_integrity_of_batch_update_place_order_ask/rule_integrity_of_batch_update_place_order_ask.conf new file mode 100644 index 000000000..0201c021c --- /dev/null +++ b/certora/CI_tests/rule_integrity_of_batch_update_place_order_ask/rule_integrity_of_batch_update_place_order_ask.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_integrity_of_batch_update_place_order_ask"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_integrity_of_batch_update_place_order_bid/expected.json b/certora/CI_tests/rule_integrity_of_batch_update_place_order_bid/expected.json new file mode 100644 index 000000000..e111df557 --- /dev/null +++ b/certora/CI_tests/rule_integrity_of_batch_update_place_order_bid/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_integrity_of_batch_update_place_order_bid": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_integrity_of_batch_update_place_order_bid/rule_integrity_of_batch_update_place_order_bid.conf b/certora/CI_tests/rule_integrity_of_batch_update_place_order_bid/rule_integrity_of_batch_update_place_order_bid.conf new file mode 100644 index 000000000..9d3eff2ff --- /dev/null +++ b/certora/CI_tests/rule_integrity_of_batch_update_place_order_bid/rule_integrity_of_batch_update_place_order_bid.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_integrity_of_batch_update_place_order_bid"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_market_claim_seat_once/expected.json b/certora/CI_tests/rule_market_claim_seat_once/expected.json new file mode 100644 index 000000000..98f1e3c21 --- /dev/null +++ b/certora/CI_tests/rule_market_claim_seat_once/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_market_claim_seat_once": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_market_claim_seat_once/rule_market_claim_seat_once.conf b/certora/CI_tests/rule_market_claim_seat_once/rule_market_claim_seat_once.conf new file mode 100644 index 000000000..b95e7f198 --- /dev/null +++ b/certora/CI_tests/rule_market_claim_seat_once/rule_market_claim_seat_once.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_market_claim_seat_once"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_market_claim_seat_twice_different_trader/expected.json b/certora/CI_tests/rule_market_claim_seat_twice_different_trader/expected.json new file mode 100644 index 000000000..420d7fd28 --- /dev/null +++ b/certora/CI_tests/rule_market_claim_seat_twice_different_trader/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_market_claim_seat_twice_different_trader": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_market_claim_seat_twice_different_trader/rule_market_claim_seat_twice_different_trader.conf b/certora/CI_tests/rule_market_claim_seat_twice_different_trader/rule_market_claim_seat_twice_different_trader.conf new file mode 100644 index 000000000..8d40e35bb --- /dev/null +++ b/certora/CI_tests/rule_market_claim_seat_twice_different_trader/rule_market_claim_seat_twice_different_trader.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_market_claim_seat_twice_different_trader"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_market_claim_seat_twice_same_trader/expected.json b/certora/CI_tests/rule_market_claim_seat_twice_same_trader/expected.json new file mode 100644 index 000000000..a2a29d932 --- /dev/null +++ b/certora/CI_tests/rule_market_claim_seat_twice_same_trader/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_market_claim_seat_twice_same_trader": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_market_claim_seat_twice_same_trader/rule_market_claim_seat_twice_same_trader.conf b/certora/CI_tests/rule_market_claim_seat_twice_same_trader/rule_market_claim_seat_twice_same_trader.conf new file mode 100644 index 000000000..9a8da4785 --- /dev/null +++ b/certora/CI_tests/rule_market_claim_seat_twice_same_trader/rule_market_claim_seat_twice_same_trader.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_market_claim_seat_twice_same_trader"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_market_deposit/expected.json b/certora/CI_tests/rule_market_deposit/expected.json new file mode 100644 index 000000000..ce9074795 --- /dev/null +++ b/certora/CI_tests/rule_market_deposit/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_market_deposit": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_market_deposit/rule_market_deposit.conf b/certora/CI_tests/rule_market_deposit/rule_market_deposit.conf new file mode 100644 index 000000000..50e5344ef --- /dev/null +++ b/certora/CI_tests/rule_market_deposit/rule_market_deposit.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_market_deposit"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_market_empty/expected.json b/certora/CI_tests/rule_market_empty/expected.json new file mode 100644 index 000000000..d4b16f049 --- /dev/null +++ b/certora/CI_tests/rule_market_empty/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_market_empty": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_market_empty/rule_market_empty.conf b/certora/CI_tests/rule_market_empty/rule_market_empty.conf new file mode 100644 index 000000000..dc645050c --- /dev/null +++ b/certora/CI_tests/rule_market_empty/rule_market_empty.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_market_empty"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_market_release_seat/expected.json b/certora/CI_tests/rule_market_release_seat/expected.json new file mode 100644 index 000000000..2491ea726 --- /dev/null +++ b/certora/CI_tests/rule_market_release_seat/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_market_release_seat": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_market_release_seat/rule_market_release_seat.conf b/certora/CI_tests/rule_market_release_seat/rule_market_release_seat.conf new file mode 100644 index 000000000..d3dfc559c --- /dev/null +++ b/certora/CI_tests/rule_market_release_seat/rule_market_release_seat.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_market_release_seat"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_place_single_order_canceled_ask/expected.json b/certora/CI_tests/rule_place_single_order_canceled_ask/expected.json new file mode 100644 index 000000000..3157ae865 --- /dev/null +++ b/certora/CI_tests/rule_place_single_order_canceled_ask/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_place_single_order_canceled_ask": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_place_single_order_canceled_ask/rule_place_single_order_canceled_ask.conf b/certora/CI_tests/rule_place_single_order_canceled_ask/rule_place_single_order_canceled_ask.conf new file mode 100644 index 000000000..bbde3fff3 --- /dev/null +++ b/certora/CI_tests/rule_place_single_order_canceled_ask/rule_place_single_order_canceled_ask.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_place_single_order_canceled_ask"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_place_single_order_canceled_bid/expected.json b/certora/CI_tests/rule_place_single_order_canceled_bid/expected.json new file mode 100644 index 000000000..4d0266601 --- /dev/null +++ b/certora/CI_tests/rule_place_single_order_canceled_bid/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_place_single_order_canceled_bid": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_place_single_order_canceled_bid/rule_place_single_order_canceled_bid.conf b/certora/CI_tests/rule_place_single_order_canceled_bid/rule_place_single_order_canceled_bid.conf new file mode 100644 index 000000000..964688f30 --- /dev/null +++ b/certora/CI_tests/rule_place_single_order_canceled_bid/rule_place_single_order_canceled_bid.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_place_single_order_canceled_bid"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_place_single_order_full_match_ask/expected.json b/certora/CI_tests/rule_place_single_order_full_match_ask/expected.json new file mode 100644 index 000000000..2dbeaee1f --- /dev/null +++ b/certora/CI_tests/rule_place_single_order_full_match_ask/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_place_single_order_full_match_ask": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_place_single_order_full_match_ask/rule_place_single_order_full_match_ask.conf b/certora/CI_tests/rule_place_single_order_full_match_ask/rule_place_single_order_full_match_ask.conf new file mode 100644 index 000000000..86fc384a3 --- /dev/null +++ b/certora/CI_tests/rule_place_single_order_full_match_ask/rule_place_single_order_full_match_ask.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_place_single_order_full_match_ask"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_place_single_order_full_match_bid/expected.json b/certora/CI_tests/rule_place_single_order_full_match_bid/expected.json new file mode 100644 index 000000000..25478faee --- /dev/null +++ b/certora/CI_tests/rule_place_single_order_full_match_bid/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_place_single_order_full_match_bid": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_place_single_order_full_match_bid/rule_place_single_order_full_match_bid.conf b/certora/CI_tests/rule_place_single_order_full_match_bid/rule_place_single_order_full_match_bid.conf new file mode 100644 index 000000000..936ba275c --- /dev/null +++ b/certora/CI_tests/rule_place_single_order_full_match_bid/rule_place_single_order_full_match_bid.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_place_single_order_full_match_bid"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_place_single_order_partial_match_ask/expected.json b/certora/CI_tests/rule_place_single_order_partial_match_ask/expected.json new file mode 100644 index 000000000..fbe61d678 --- /dev/null +++ b/certora/CI_tests/rule_place_single_order_partial_match_ask/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_place_single_order_partial_match_ask": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_place_single_order_partial_match_ask/rule_place_single_order_partial_match_ask.conf b/certora/CI_tests/rule_place_single_order_partial_match_ask/rule_place_single_order_partial_match_ask.conf new file mode 100644 index 000000000..af12e2cde --- /dev/null +++ b/certora/CI_tests/rule_place_single_order_partial_match_ask/rule_place_single_order_partial_match_ask.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_place_single_order_partial_match_ask"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_place_single_order_partial_match_bid/expected.json b/certora/CI_tests/rule_place_single_order_partial_match_bid/expected.json new file mode 100644 index 000000000..6317c2536 --- /dev/null +++ b/certora/CI_tests/rule_place_single_order_partial_match_bid/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_place_single_order_partial_match_bid": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_place_single_order_partial_match_bid/rule_place_single_order_partial_match_bid.conf b/certora/CI_tests/rule_place_single_order_partial_match_bid/rule_place_single_order_partial_match_bid.conf new file mode 100644 index 000000000..517a5334b --- /dev/null +++ b/certora/CI_tests/rule_place_single_order_partial_match_bid/rule_place_single_order_partial_match_bid.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_place_single_order_partial_match_bid"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_place_single_order_unmatched_ask/expected.json b/certora/CI_tests/rule_place_single_order_unmatched_ask/expected.json new file mode 100644 index 000000000..86cbd6284 --- /dev/null +++ b/certora/CI_tests/rule_place_single_order_unmatched_ask/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_place_single_order_unmatched_ask": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_place_single_order_unmatched_ask/rule_place_single_order_unmatched_ask.conf b/certora/CI_tests/rule_place_single_order_unmatched_ask/rule_place_single_order_unmatched_ask.conf new file mode 100644 index 000000000..a1f47615c --- /dev/null +++ b/certora/CI_tests/rule_place_single_order_unmatched_ask/rule_place_single_order_unmatched_ask.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_place_single_order_unmatched_ask"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_place_single_order_unmatched_bid/expected.json b/certora/CI_tests/rule_place_single_order_unmatched_bid/expected.json new file mode 100644 index 000000000..487605e49 --- /dev/null +++ b/certora/CI_tests/rule_place_single_order_unmatched_bid/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_place_single_order_unmatched_bid": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_place_single_order_unmatched_bid/rule_place_single_order_unmatched_bid.conf b/certora/CI_tests/rule_place_single_order_unmatched_bid/rule_place_single_order_unmatched_bid.conf new file mode 100644 index 000000000..bc98a2b33 --- /dev/null +++ b/certora/CI_tests/rule_place_single_order_unmatched_bid/rule_place_single_order_unmatched_bid.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_place_single_order_unmatched_bid"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_rest_remaining_ask/expected.json b/certora/CI_tests/rule_rest_remaining_ask/expected.json new file mode 100644 index 000000000..d02ffcbec --- /dev/null +++ b/certora/CI_tests/rule_rest_remaining_ask/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_rest_remaining_ask": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_rest_remaining_ask/rule_rest_remaining_ask.conf b/certora/CI_tests/rule_rest_remaining_ask/rule_rest_remaining_ask.conf new file mode 100644 index 000000000..c61eae7f5 --- /dev/null +++ b/certora/CI_tests/rule_rest_remaining_ask/rule_rest_remaining_ask.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_rest_remaining_ask"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_rest_remaining_bid/expected.json b/certora/CI_tests/rule_rest_remaining_bid/expected.json new file mode 100644 index 000000000..54cb492a4 --- /dev/null +++ b/certora/CI_tests/rule_rest_remaining_bid/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_rest_remaining_bid": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_rest_remaining_bid/rule_rest_remaining_bid.conf b/certora/CI_tests/rule_rest_remaining_bid/rule_rest_remaining_bid.conf new file mode 100644 index 000000000..f9ed16cfa --- /dev/null +++ b/certora/CI_tests/rule_rest_remaining_bid/rule_rest_remaining_bid.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_rest_remaining_bid"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_swap_base_exact/expected.json b/certora/CI_tests/rule_swap_base_exact/expected.json new file mode 100644 index 000000000..54b21f607 --- /dev/null +++ b/certora/CI_tests/rule_swap_base_exact/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_swap_base_exact": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_swap_base_exact/rule_swap_base_exact.conf b/certora/CI_tests/rule_swap_base_exact/rule_swap_base_exact.conf new file mode 100644 index 000000000..521f96b96 --- /dev/null +++ b/certora/CI_tests/rule_swap_base_exact/rule_swap_base_exact.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_swap_base_exact"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_swap_base_not_exact/expected.json b/certora/CI_tests/rule_swap_base_not_exact/expected.json new file mode 100644 index 000000000..d368dab5c --- /dev/null +++ b/certora/CI_tests/rule_swap_base_not_exact/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_swap_base_not_exact": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_swap_base_not_exact/rule_swap_base_not_exact.conf b/certora/CI_tests/rule_swap_base_not_exact/rule_swap_base_not_exact.conf new file mode 100644 index 000000000..f424d5189 --- /dev/null +++ b/certora/CI_tests/rule_swap_base_not_exact/rule_swap_base_not_exact.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_swap_base_not_exact"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_swap_quote_exact/expected.json b/certora/CI_tests/rule_swap_quote_exact/expected.json new file mode 100644 index 000000000..b75fddb88 --- /dev/null +++ b/certora/CI_tests/rule_swap_quote_exact/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_swap_quote_exact": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_swap_quote_exact/rule_swap_quote_exact.conf b/certora/CI_tests/rule_swap_quote_exact/rule_swap_quote_exact.conf new file mode 100644 index 000000000..1978d45f1 --- /dev/null +++ b/certora/CI_tests/rule_swap_quote_exact/rule_swap_quote_exact.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_swap_quote_exact"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_swap_quote_not_exact/expected.json b/certora/CI_tests/rule_swap_quote_not_exact/expected.json new file mode 100644 index 000000000..994949d6c --- /dev/null +++ b/certora/CI_tests/rule_swap_quote_not_exact/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_swap_quote_not_exact": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_swap_quote_not_exact/rule_swap_quote_not_exact.conf b/certora/CI_tests/rule_swap_quote_not_exact/rule_swap_quote_not_exact.conf new file mode 100644 index 000000000..ed807cf65 --- /dev/null +++ b/certora/CI_tests/rule_swap_quote_not_exact/rule_swap_quote_not_exact.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_swap_quote_not_exact"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_update_balance/expected.json b/certora/CI_tests/rule_update_balance/expected.json new file mode 100644 index 000000000..b819914a0 --- /dev/null +++ b/certora/CI_tests/rule_update_balance/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_update_balance": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_update_balance/rule_update_balance.conf b/certora/CI_tests/rule_update_balance/rule_update_balance.conf new file mode 100644 index 000000000..8402e494f --- /dev/null +++ b/certora/CI_tests/rule_update_balance/rule_update_balance.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_update_balance"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_withdraw_base/expected.json b/certora/CI_tests/rule_withdraw_base/expected.json new file mode 100644 index 000000000..34f883241 --- /dev/null +++ b/certora/CI_tests/rule_withdraw_base/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_withdraw_base": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_withdraw_base/rule_withdraw_base.conf b/certora/CI_tests/rule_withdraw_base/rule_withdraw_base.conf new file mode 100644 index 000000000..bf03492f0 --- /dev/null +++ b/certora/CI_tests/rule_withdraw_base/rule_withdraw_base.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_withdraw_base"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_withdraw_does_not_revert/expected.json b/certora/CI_tests/rule_withdraw_does_not_revert/expected.json new file mode 100644 index 000000000..2ba18b8e5 --- /dev/null +++ b/certora/CI_tests/rule_withdraw_does_not_revert/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_withdraw_does_not_revert": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_withdraw_does_not_revert/rule_withdraw_does_not_revert.conf b/certora/CI_tests/rule_withdraw_does_not_revert/rule_withdraw_does_not_revert.conf new file mode 100644 index 000000000..5f8914d03 --- /dev/null +++ b/certora/CI_tests/rule_withdraw_does_not_revert/rule_withdraw_does_not_revert.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_withdraw_does_not_revert"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_withdraw_quote/expected.json b/certora/CI_tests/rule_withdraw_quote/expected.json new file mode 100644 index 000000000..df47fef37 --- /dev/null +++ b/certora/CI_tests/rule_withdraw_quote/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_withdraw_quote": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_withdraw_quote/rule_withdraw_quote.conf b/certora/CI_tests/rule_withdraw_quote/rule_withdraw_quote.conf new file mode 100644 index 000000000..0500a19ad --- /dev/null +++ b/certora/CI_tests/rule_withdraw_quote/rule_withdraw_quote.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_withdraw_quote"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/rule_withdraw_withdraws/expected.json b/certora/CI_tests/rule_withdraw_withdraws/expected.json new file mode 100644 index 000000000..01738544c --- /dev/null +++ b/certora/CI_tests/rule_withdraw_withdraws/expected.json @@ -0,0 +1,6 @@ +{ + "assertMessages":{}, + "rules": { + "rule_withdraw_withdraws": "SUCCESS" + } +} diff --git a/certora/CI_tests/rule_withdraw_withdraws/rule_withdraw_withdraws.conf b/certora/CI_tests/rule_withdraw_withdraws/rule_withdraw_withdraws.conf new file mode 100644 index 000000000..e32e1086b --- /dev/null +++ b/certora/CI_tests/rule_withdraw_withdraws/rule_withdraw_withdraws.conf @@ -0,0 +1,6 @@ +{ + "files": ["../../../target/sbf-solana-solana/release/manifest.so"], + "java_args" : ["-Dlevel.ebpf=info"], + "prover_args" :["-solanaOptimisticJoin true -solanaOptimisticOverlaps true -solanaOptimisticMemcpyPromotion true -solanaOptimisticMemcmp true -solanaOptimisticNoMemmove true -solanaInlining ../../cvt_inlining.txt -solanaSummaries ../../cvt_summaries.txt -solanaEntrypoint rule_withdraw_withdraws"], + "rule_sanity": "basic" +} diff --git a/certora/CI_tests/test_ignore.txt b/certora/CI_tests/test_ignore.txt new file mode 100644 index 000000000..e69de29bb diff --git a/certora/cvt_inlining.txt b/certora/cvt_inlining.txt new file mode 100644 index 000000000..4a24e8ab9 --- /dev/null +++ b/certora/cvt_inlining.txt @@ -0,0 +1,163 @@ +; By default we do not inline core, std, alloc, and solana_program +; with some exceptions below with #[inline] + +#[inline(never)] ^core::.*$ +#[inline(never)] ^std::.*$ +#[inline(never)] ^::get$ +#[inline] ^solana_program::poseidon::PoseidonHash::new$ +#[inline] ^solana_program::account_info::AccountInfo::assign$ +#[inline] ^solana_program::incinerator::check_id$ +#[inline] ^solana_program::system_program::check_id$ +#[inline] ^solana_program::system_program::id$ +#[inline] ^solana_program::rent::Rent::minimum_balance$ +#[inline] ^solana_program::sysvar::rent::::get$ +#[inline] ^solana_program::instruction::get_stack_height$ +#[inline] ^solana_program::program::set_return_data$ + +#[inline(never)] ^>::from$ + +#[inline] ^core::result::unwrap_failed$ +#[inline] ^core::cell::RefCell::borrow(_\d+)?$ +#[inline] ^core::cell::RefCell::borrow_mut(_\d+)?$ + + +;; Borsh and common functions used by Borsh +#[inline(never)] ^std::io::error::Error::new(_\d+)?$ +#[inline(never)] ^borsh::de::unexpected_eof_to_unexpected_length_of_input$ + + +;; We need to inline this function to avoid unsoundness results in +;; NcnOperatorTicket::seeds and others. +#[inline] ^ as alloc::vec::spec_from_iter::SpecFromIter>::from_iter(_\d+)?$ diff --git a/certora/cvt_summaries.txt b/certora/cvt_summaries.txt new file mode 100644 index 000000000..a258decc0 --- /dev/null +++ b/certora/cvt_summaries.txt @@ -0,0 +1,89 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; POINTS-TO SUMMARIES +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;; if the call returns then (*i64)(r1+0) is always a valid pointer. +;;; 1st call: +;;; - precondition: (*i64)(r1+0) is a Rust dangling pointer +;;; - post-condition: (*i64)(r1+0) points to new allocated memory (malloc) +;;; 2nd call: +;;; - precondition: (*i64)(r1+0) is a valid pointer +;;; - post-condition: (*i64)(r1+0) points to a new allocated memory after resizing the memory object +;;; to which r1 pointed to before the call (realloc). +#[type((*i64)(r1+0):ptr_heap)] +^alloc::raw_vec::RawVec::reserve_for_push(_[0-9][0-9]*)*$ +#[type((*i64)(r1+0):ptr_heap)] +^alloc::raw_vec::RawVec::reserve::do_reserve_and_handle(_[0-9][0-9]*)*$ + +#[type((*i64)(r1+0):num)] +#[type((*i64)(r1+8):num)] +^__multi3$ + +#[type((*i64)(r1+0):num)] +#[type((*i64)(r1+8):num)] +^__udivti3$ + +#[type((*i64)(r1+0):num)] +#[type((*i64)(r1+8):num)] +^__divti3$ + +#[type(r0:num)] +^__muldf3$ + +#[type(r0:num)] +^__divdf3$ + +#[type((*i64)(r1+0):num)] +#[type((*i64)(r1+8):num)] +#[type((*i64)(r1+16):num)] +#[type((*i64)(r1+24):num)] +#[type((*i64)(r1+32):num)] +^sol_get_clock_sysvar$ + +;; %"AccountInfo" = type { %"Pubkey"*, i64*, i64*, %"Pubkey"*, i64, i8, i8, i8, [5 x i8] } +#[type((*i64)(r1+0):ptr_external)] +#[type((*i64)(r1+8):ptr_external)] +#[type((*i64)(r1+16):ptr_external)] +#[type((*i64)(r1+24):ptr_external)] +#[type((*i64)(r1+32):num)] +#[type((*i8)(r1+40):num)] +#[type((*i8)(r1+41):num)] +#[type((*i8)(r1+42):num)] +^([^:]+::)*CVT_nondet_account_info$ + +#[type((*i64)(r1+0):num)] +#[type((*i64)(r1+8):num)] +#[type((*i64)(r1+16):num)] +#[type((*i64)(r1+24):num)] +^([^:]+::)*CVT_nondet_pubkey$ + +#[type(r0:ptr_external)] +^([^:]+::)*CVT_nondet_pointer_usize$ + +#[type((*i32)(r1+0):num)] +^solana_program::account_info::AccountInfo::realloc$ + +;; Result +#[type((*i8)(r1+0):num)] +#[type((*i64)(r1+1):num)] +#[type((*i64)(r1+9):num)] +#[type((*i64)(r1+17):num)] +#[type((*i64)(r1+25):num)] +^solana_program::pubkey::Pubkey::create_program_address$ + +;; (Pubkey, u8) +#[type((*i64)(r1+0):num)] +#[type((*i64)(r1+8):num)] +#[type((*i64)(r1+16):num)] +#[type((*i64)(r1+24):num)] +#[type((*i8)(r1+32):num)] +^solana_program::pubkey::Pubkey::find_program_address$ + + +#[type((*i32)(r1+0):num)] +^solana_program::program::invoke_signed_unchecked$ + +;; To silent Solana prover warning about memhavoc_c +^memhavoc_c$ diff --git a/client/rust/Cargo.toml b/client/rust/Cargo.toml index e19306f95..bf98f37bc 100644 --- a/client/rust/Cargo.toml +++ b/client/rust/Cargo.toml @@ -14,7 +14,7 @@ test = [] anyhow = { workspace = true } manifest = { path = "../../programs/manifest", features=['no-clock'] } hypertree = { path = "../../lib" } -jupiter-amm-interface = "0.4.5" +jupiter-amm-interface = "=0.4.5" solana-sdk = { workspace = true } solana-program = { workspace = true } spl-token = { workspace = true } diff --git a/lib/Cargo.toml b/lib/Cargo.toml index b257359f7..39c4627e0 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -13,10 +13,18 @@ fuzz = [] trace = [] test = [] +# Certora feature exposes many of the internal implementation details, so should +# only be used in formal verification. Ironically, the security testing makes +# the code less secure. +certora = ["dep:nondet", "dep:calltrace", "dep:cvt"] + [dependencies] bytemuck = { workspace = true } solana-program = { workspace = true } static_assertions = { workspace = true } +nondet = { workspace = true, optional = true} +calltrace = { workspace = true, optional = true} +cvt = { workspace = true, optional = true} # Does not work with sbf. Enable when debugging red black only colored = { version = "2.1.0", optional = true } \ No newline at end of file diff --git a/lib/src/hypertree.rs b/lib/src/hypertree.rs index 5805778bf..edcf2632c 100644 --- a/lib/src/hypertree.rs +++ b/lib/src/hypertree.rs @@ -4,7 +4,50 @@ use bytemuck::{Pod, Zeroable}; use crate::DataIndex; -pub const NIL: DataIndex = DataIndex::MAX; +// Set to less than DataIndex::MAX because formal verification required it. It +// would be better to set it fully to DataIndex::MAX, but not a major concern +// because it is just set to an unreacahable data index and SVM limits the +// account size to 10MB. +pub const NIL: DataIndex = 0x7F_FF_FF_FF; + +#[cfg(feature = "certora")] +#[macro_export] +macro_rules! is_not_nil { + ($v: expr) => { + $v < NIL + }; +} + +#[cfg(feature = "certora")] +#[macro_export] +macro_rules! is_nil { + ($v: expr) => { + $v >= NIL + }; +} + +#[cfg(not(feature = "certora"))] +#[macro_export] +macro_rules! is_not_nil { + ($v: expr) => { + $v != NIL + }; +} + +#[cfg(not(feature = "certora"))] +#[macro_export] +macro_rules! is_nil { + ($v: expr) => { + $v == NIL + }; +} + +#[macro_export] +macro_rules! eq_nil { + ($v: expr) => { + $v == NIL + }; +} pub trait Payload: Zeroable + Pod + PartialOrd + Ord + PartialEq + Eq + Display {} impl Payload for T {} diff --git a/lib/src/red_black_tree.rs b/lib/src/red_black_tree.rs index 63fc5492c..e87dbeba3 100644 --- a/lib/src/red_black_tree.rs +++ b/lib/src/red_black_tree.rs @@ -177,6 +177,25 @@ impl<'a, V: Payload> GetRedBlackTreeData<'a> for RedBlackTree<'a, V> { } } +// Public just for certora. +#[cfg(feature = "certora")] +pub trait RedBlackTreeReadOperationsHelpers<'a> { + fn get_value(&'a self, index: DataIndex) -> &'a V; + fn has_left(&self, index: DataIndex) -> bool; + fn has_right(&self, index: DataIndex) -> bool; + fn get_right_index(&self, index: DataIndex) -> DataIndex; + fn get_left_index(&self, index: DataIndex) -> DataIndex; + fn get_color(&self, index: DataIndex) -> Color; + fn get_parent_index(&self, index: DataIndex) -> DataIndex; + fn is_left_child(&self, index: DataIndex) -> bool; + fn is_right_child(&self, index: DataIndex) -> bool; + fn get_node(&'a self, index: DataIndex) -> &RBNode; + fn get_child_index(&self, index: DataIndex) -> DataIndex; + fn is_internal(&self, index: DataIndex) -> bool; + fn get_sibling_index(&self, index: DataIndex, parent_index: DataIndex) + -> DataIndex; +} +#[cfg(not(feature = "certora"))] pub(crate) trait RedBlackTreeReadOperationsHelpers<'a> { fn get_value(&'a self, index: DataIndex) -> &'a V; fn has_left(&self, index: DataIndex) -> bool; @@ -296,6 +315,19 @@ where } } +// Public just for certora. +#[cfg(feature = "certora")] +pub trait RedBlackTreeWriteOperationsHelpers<'a> { + fn set_color(&mut self, index: DataIndex, color: Color); + fn set_parent_index(&mut self, index: DataIndex, parent_index: DataIndex); + fn set_left_index(&mut self, index: DataIndex, left_index: DataIndex); + fn set_right_index(&mut self, index: DataIndex, right_index: DataIndex); + fn rotate_left(&mut self, index: DataIndex); + fn rotate_right(&mut self, index: DataIndex); + fn swap_node_with_successor(&mut self, index_0: DataIndex, index_1: DataIndex); + fn update_parent_child(&mut self, index: DataIndex); +} +#[cfg(not(feature = "certora"))] pub(crate) trait RedBlackTreeWriteOperationsHelpers<'a> { fn set_color(&mut self, index: DataIndex, color: Color); fn set_parent_index(&mut self, index: DataIndex, parent_index: DataIndex); @@ -885,6 +917,15 @@ impl<'a, T: HyperTreeReadOperations<'a> + GetRedBlackTreeReadOnlyData<'a>, V: Pa } } +#[cfg(feature = "certora")] +#[repr(u8)] +#[derive(Debug, Copy, Clone, PartialEq, Default)] +pub enum Color { + #[default] + Black = 0, + Red = 1, +} +#[cfg(not(feature = "certora"))] #[repr(u8)] #[derive(Debug, Copy, Clone, PartialEq, Default)] pub(crate) enum Color { @@ -898,6 +939,36 @@ unsafe impl Zeroable for Color { } } +#[cfg(feature = "certora")] +impl nondet::Nondet for Color { + fn nondet() -> Self { + if nondet::nondet::() { + Color::Black + } else { + Color::Red + } + } +} + +#[cfg(feature = "certora")] +#[derive(Debug, Default, Copy, Clone, Zeroable)] +#[repr(C)] +/// Node in a RedBlack tree. The first 16 bytes are used for maintaining the +/// RedBlack and BST properties, the rest is the payload. +pub struct RBNode { + pub left: DataIndex, + pub right: DataIndex, + pub parent: DataIndex, + pub color: Color, + + // Optional enum controlled by the application to identify the type of node. + // Defaults to zero. + pub payload_type: u8, + + pub _unused_padding: u16, + pub value: V, +} +#[cfg(not(feature = "certora"))] #[derive(Debug, Default, Copy, Clone, Zeroable)] #[repr(C)] /// Node in a RedBlack tree. The first 16 bytes are used for maintaining the @@ -1102,6 +1173,16 @@ impl<'a, V: Payload> RedBlackTree<'a, V> { self.remove_by_index(index); } + // Only publicly visible for formal verification. + #[cfg(feature = "certora")] + pub fn certora_remove_fix( + &mut self, + current_index: DataIndex, + parent_index: DataIndex, + ) -> (DataIndex, DataIndex) { + self.remove_fix(current_index, parent_index) + } + fn remove_fix( &mut self, current_index: DataIndex, @@ -1258,6 +1339,12 @@ impl<'a, V: Payload> RedBlackTree<'a, V> { } } + // Only publicly visible for formal verification. + #[cfg(feature = "certora")] + pub fn certora_insert_fix(&mut self, index_to_fix: DataIndex) -> DataIndex { + self.insert_fix(index_to_fix) + } + fn insert_fix(&mut self, index_to_fix: DataIndex) -> DataIndex { if self.root_index == index_to_fix { self.set_color::(index_to_fix, Color::Black); diff --git a/lib/src/utils.rs b/lib/src/utils.rs index 45aa08a7d..69ff489b1 100644 --- a/lib/src/utils.rs +++ b/lib/src/utils.rs @@ -42,6 +42,7 @@ fn test_pod_bool() { } #[macro_export] +#[cfg(not(feature = "certora"))] macro_rules! trace { ($($arg:tt)*) => { #[cfg(feature = "trace")] @@ -57,3 +58,9 @@ macro_rules! trace { } }; } + +#[macro_export] +#[cfg(feature = "certora")] +macro_rules! trace { + ($($arg:tt)*) => {}; +} diff --git a/programs/manifest/Cargo.toml b/programs/manifest/Cargo.toml index 26fd542c4..45ac06a84 100644 --- a/programs/manifest/Cargo.toml +++ b/programs/manifest/Cargo.toml @@ -19,6 +19,10 @@ test = [] fuzz = [] trace = ["hypertree/trace"] no-clock = [] +certora = ["no-entrypoint", "dep:cvt", "dep:nondet", "dep:cvt-macros", "dep:early-panic", "dep:calltrace", "dep:solana_cvt", "dep:vectors", "dep:hook_macro", + "hypertree/certora"] +certora_vacuity = ["cvt/vacuity"] + # https://doc.rust-lang.org/cargo/reference/profiles.html [profile.release] @@ -49,6 +53,15 @@ num_enum = { workspace = true } thiserror = { workspace = true } solana-security-txt = { workspace = true } static_assertions = { workspace = true } +nondet = { workspace = true, optional = true} +cvt = { workspace = true, optional = true} +cvt-macros = { workspace = true, optional = true} +early-panic = { workspace = true, optional = true} +calltrace = { workspace = true, optional = true} +solana_cvt = { workspace = true, optional = true} +vectors = { workspace = true, optional = true} +hook_macro = { workspace = true, optional = true} +arrayref = { workspace = true} solana-invoke = { workspace = true } [dev-dependencies] diff --git a/programs/manifest/justfile b/programs/manifest/justfile new file mode 100644 index 000000000..c7066029d --- /dev/null +++ b/programs/manifest/justfile @@ -0,0 +1,135 @@ +# user settings should be placed into .env file in some ancestor directory +set dotenv-load + +# used by OSX, ignore otherwise +export CPATH := env_var_or_default("CPATH", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include") +# magic llvm flags +export RUSTFLAGS := "-C llvm-args=--sbf-expand-memcpy-in-order -C llvm-args=--combiner-store-merging=false" +# features used when compiling target Rust code +export CARGO_FEATURES := env_var_or_default("CARGO_FEATURES", "") +# java executable +export JAVA := env_var_or_default("JAVA", "java") +# certora cli: certoraRun or certoraRun.py +export CERTORA_CLI := env_var_or_default("CERTORA_CLI", "certoraRun") +# run split or single solver mode +export CERTORA_SPLIT := env_var_or_default("CERTORA_SPLIT", "false") +# produce unsat cores +export CERTORA_UNSAT_CORES := env_var_or_default("CERTORA_UNSAT_CORES", "false") + +# type of cloud to use +certora_cloud := "prover" +# cloud version of the prover (if remote is used) +prover_version := "jorge/solana-jsm" + +# location of prover jar +emv_jar := "$CERTORA/emv.jar" + +# location of all local files + +# this justfile is expected to be imported from others +# this is set in the top-level justfile +jsm_root := canonicalize("../..") +certora-scripts := jsm_root / "certora" +inliner_cfg := certora-scripts / "cvt_inlining.txt" +summaries_cfg := certora-scripts / "cvt_summaries.txt" +file := jsm_root / "target/sbf-solana-solana/release/manifest.so" + +remote_jsm_root := "../.." +remote_certora-scripts := remote_jsm_root / "certora" +remote_inliner_cfg := remote_certora-scripts / "cvt_inlining.txt" +remote_summaries_cfg := remote_certora-scripts / "cvt_summaries.txt" +remote_file := remote_jsm_root / "target/sbf-solana-solana/release/manifest.so" + + +doc: + cargo doc --lib -F certora +test *TESTS: + cargo test {{TESTS}} -- --nocapture +test-certora *TESTS: + cargo test --features certora-test {{TESTS}} -- --nocapture + +build-sbf extra_features="": + echo "env RUSTFLAGS=$RUSTFLAGS" + echo "env CARGO_FEATURES=$CARGO_FEATURES" + cargo +solana build-sbf --features certora {{ extra_features }} ${CARGO_FEATURES} + +build-sbf-llvm: + env RUSTFLAGS="${RUSTFLAGS} --emit=llvm-ir -C no-vectorize-slp -C opt-level=2" \ + cargo +solana build-sbf --features certora + +build: + cargo build + +cvt-update: + cargo update -p nondet + cargo update -p cvt + +# Usage: verify name_of_rule extra_options +verify RULE *OPTS: build-sbf + mkdir -p certora_out + cd certora_out && ${JAVA} -ea -Xmx8g \ + -Dtopic.spec -Dlevel.ebpf=${CERTORA_VERBOSE:-info} \ + -Dverbose.times -Dcvt.simple.parallel -Djava.awt.headless=true \ + -jar {{ emv_jar }} \ + -deleteSMTFile false -graphDrawLimit 2000 \ + {{ file }} \ + -split ${CERTORA_SPLIT} \ + -unsatCoresForAllAsserts ${CERTORA_UNSAT_CORES} \ + -solanaInlining {{ inliner_cfg }} \ + -solanaSummaries {{ summaries_cfg }} \ + -solanaOptimisticJoin true \ + -solanaOptimisticOverlaps true \ + -solanaOptimisticMemcpyPromotion true \ + -solanaOptimisticMemcmp true \ + -solanaOptimisticNoMemmove true \ + -solanaPrintAnalyzedToDot \ + -solanaEntrypoint {{ RULE }} \ + {{ OPTS }} + +# Usage: vacuity name_of_rule extra_options +vacuity RULE *OPTS: (build-sbf "certora_vacuity") + mkdir -p certora_out + cd certora_out && ${JAVA} -ea -Xmx8g \ + -Dtopic.spec -Dlevel.ebpf=${CERTORA_VERBOSE:-info} \ + -Dverbose.times -Dcvt.simple.parallel -Djava.awt.headless=true \ + -jar {{ emv_jar }} \ + -deleteSMTFile false -graphDrawLimit 2000 \ + {{ file }} \ + -solanaInlining {{ inliner_cfg }} \ + -solanaSummaries {{ summaries_cfg }} \ + -solanaOptimisticJoin true \ + -solanaOptimisticOverlaps true \ + -solanaOptimisticMemcpyPromotion true \ + -solanaOptimisticMemcmp true \ + -solanaOptimisticNoMemmove true \ + -solanaPrintAnalyzedToDot \ + -solanaEntrypoint {{ RULE }} \ + {{ OPTS }} + +# Usage: verify-remote name_of_rule extra_options +verify-remote RULE *OPTS: build-sbf + ${CERTORA_CLI} {{ remote_file }} \ + {{ OPTS }} \ + --prover_args \ + "-solanaInlining {{ remote_inliner_cfg }} \ + -solanaSummaries {{ remote_summaries_cfg }} \ + -solanaOptimisticJoin true \ + -solanaOptimisticOverlaps true \ + -solanaOptimisticMemcpyPromotion true \ + -solanaOptimisticMemcmp true \ + -solanaOptimisticNoMemmove true \ + -solanaEntrypoint {{ RULE }}" \ + --prover_version {{ prover_version }} \ + --server {{ certora_cloud }} + + +clean: + rm -f *.dot *.png *.svg + rm -f log_verification* + rm -Rf emv-* + rm -Rf .certora_internal + rm -f log.txt + rm -rf certora_out + + + diff --git a/programs/manifest/rules-rb-tree.json b/programs/manifest/rules-rb-tree.json new file mode 100644 index 000000000..e5b14c96e --- /dev/null +++ b/programs/manifest/rules-rb-tree.json @@ -0,0 +1,190 @@ +{ + "rules": [ + { + "name": "rule_rotate_left", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_rotate_right", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_root_is_black_after_insert_empty_tree", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_insert_fix_matches_reference_no_parent", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_insert_fix_matches_reference_case1_left_child", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_insert_fix_matches_reference_case2_left_child", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_insert_fix_matches_reference_case3_left_child", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_insert_fix_matches_reference_case3_right_child", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_insert_fix_matches_reference_case2_right_child", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_insert_fix_matches_reference_case1_right_child", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_remove_fix_matches_reference_case1_left_child", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_remove_fix_matches_reference_case2_left_child", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_remove_fix_matches_reference_case3_left_child", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_remove_fix_matches_reference_case4_left_child", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_remove_fix_matches_reference_case1_right_child", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_remove_fix_matches_reference_case2_right_child", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_remove_fix_matches_reference_case3_right_child", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_remove_fix_matches_reference_case4_right_child", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_insert_updates_max_index_empty_tree", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_insert_updates_max_index_non_empty_tree_max", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_insert_updates_max_index_non_empty_tree_not_max", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_remove_updates_max_index_single_node_tree", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_remove_updates_max_index_non_empty_tree_max", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_remove_updates_max_index_non_empty_tree_not_max", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_swap_internal_nodes_left_children", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_swap_internal_nodes_right_children", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_swap_internal_nodes_first_is_root", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_swap_nodes_with_one_child_left_right", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_swap_nodes_with_one_child_right_left", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_swap_leaves", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_swap_parent_right_child", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + } + ] +} \ No newline at end of file diff --git a/programs/manifest/rules.json b/programs/manifest/rules.json new file mode 100644 index 000000000..c0b3956e3 --- /dev/null +++ b/programs/manifest/rules.json @@ -0,0 +1,292 @@ +{ + "rules": [ + { + "name": "rule_market_empty", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_market_deposit", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_deposit_deposits", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_withdraw_withdraws", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_withdraw_does_not_revert", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_update_balance", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_deposit_base", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_deposit_quote", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_withdraw_base", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_withdraw_quote", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_cancel_order_by_index_ask", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_cancel_order_ask", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_cancel_order_by_index_no_revert_bid", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_cancel_order_by_index_no_revert_ask", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_place_single_order_canceled_bid", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_place_single_order_canceled_ask", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_place_single_order_unmatched_bid", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_place_single_order_unmatched_ask", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_place_single_order_full_match_bid", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_place_single_order_full_match_ask", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_place_single_order_partial_match_bid", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_place_single_order_partial_match_ask", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_market_claim_seat_once", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_market_claim_seat_twice_same_trader", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_market_claim_seat_twice_different_trader", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_rest_remaining_bid", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_rest_remaining_ask", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_market_release_seat", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_integrity_of_batch_update_cancel_bid", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_integrity_of_batch_update_cancel_ask", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_integrity_of_batch_update_cancel_hint_bid", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_integrity_of_batch_update_cancel_hint_ask", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_integrity_of_batch_update_place_order_bid", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_integrity_of_batch_update_place_order_ask", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_matching_if_maker_order_exists_bid", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_matching_if_maker_order_exists_ask", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_crossed_prices_if_matched_bid", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_crossed_prices_if_matched_ask", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_place_single_order_full_match_balances_bid", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_place_single_order_full_match_balances_ask", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_place_single_order_partial_match_balances_bid", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_place_single_order_partial_match_balances_ask", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_matching_order_removed_if_fully_matched_bid", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_matching_order_removed_if_fully_matched_ask", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_matching_fully_matched_if_order_removed_bid", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_matching_fully_matched_if_order_removed_ask", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_matching_decrease_maker_order_atoms_bid", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + }, + { + "name": "rule_matching_decrease_maker_order_atoms_ask", + "expected_result": "Verified", + "prover_options": [], + "cargo_features": [] + } + ] +} diff --git a/programs/manifest/src/certora/hooks.rs b/programs/manifest/src/certora/hooks.rs new file mode 100644 index 000000000..4ff09f92e --- /dev/null +++ b/programs/manifest/src/certora/hooks.rs @@ -0,0 +1,66 @@ +#[derive(Clone, Copy, Ord, Eq, PartialEq, PartialOrd)] +enum CvtManifestOrder { + None, + CancelOrder, + CancelOrderByIndex, + PlaceOrder, +} + +/// Keep track of which order was executed +static mut LAST_ORDER_EXECUTED: CvtManifestOrder = CvtManifestOrder::None; + +/// Keep track of whether remove_order_from_tree_and_free was called +static mut ORDER_REMOVED: bool = false; + +// Initialization + +pub fn initialize_hooks() { + unsafe { + LAST_ORDER_EXECUTED = CvtManifestOrder::None; + ORDER_REMOVED = false; + } +} + +// Setters + +pub fn cancel_order_was_called() { + unsafe { + LAST_ORDER_EXECUTED = CvtManifestOrder::CancelOrder; + } +} + +pub fn cancel_order_by_index_was_called() { + unsafe { + LAST_ORDER_EXECUTED = CvtManifestOrder::CancelOrderByIndex; + } +} + +pub fn place_order_was_called() { + unsafe { + LAST_ORDER_EXECUTED = CvtManifestOrder::PlaceOrder; + } +} + +pub fn remove_order_from_tree_and_free_was_called() { + unsafe { + ORDER_REMOVED = true; + } +} + +// Getters + +pub fn last_called_cancel_order() -> bool { + unsafe { LAST_ORDER_EXECUTED == CvtManifestOrder::CancelOrder } +} + +pub fn last_called_cancel_order_by_index() -> bool { + unsafe { LAST_ORDER_EXECUTED == CvtManifestOrder::CancelOrderByIndex } +} + +pub fn last_called_place_order() -> bool { + unsafe { LAST_ORDER_EXECUTED == CvtManifestOrder::PlaceOrder } +} + +pub fn last_called_remove_order_from_tree_and_free() -> bool { + unsafe { ORDER_REMOVED == true } +} diff --git a/programs/manifest/src/certora/mocks_batch_update.rs b/programs/manifest/src/certora/mocks_batch_update.rs new file mode 100644 index 000000000..a13cca791 --- /dev/null +++ b/programs/manifest/src/certora/mocks_batch_update.rs @@ -0,0 +1,33 @@ +use crate::certora::hooks::*; +use hook_macro::cvt_hook_end; +use nondet::nondet; + +use crate::{ + state::{AddOrderToMarketArgs, AddOrderToMarketResult, MarketRefMut}, + validation::loaders::GlobalTradeAccounts, +}; +use hypertree::DataIndex; +use solana_program::{entrypoint::ProgramResult, program_error::ProgramError}; + +#[cfg_attr(feature = "certora", cvt_hook_end(cancel_order_was_called()))] +pub fn mock_cancel_order( + _dynamic_account: &MarketRefMut, + _trader_index: DataIndex, + _order_sequence_number: u64, + _global_trade_accounts_opts: &[Option; 2], +) -> ProgramResult { + Ok(()) +} + +#[cfg_attr(feature = "certora", cvt_hook_end(place_order_was_called()))] +pub fn mock_place_order( + _dynamic_account: &MarketRefMut, + _args: AddOrderToMarketArgs, +) -> Result { + Ok(AddOrderToMarketResult { + order_sequence_number: nondet(), + order_index: nondet(), + base_atoms_traded: nondet(), + quote_atoms_traded: nondet(), + }) +} diff --git a/programs/manifest/src/certora/mod.rs b/programs/manifest/src/certora/mod.rs new file mode 100644 index 000000000..201f51715 --- /dev/null +++ b/programs/manifest/src/certora/mod.rs @@ -0,0 +1,6 @@ +//! Certora specs +pub(crate) mod hooks; +pub(crate) mod mocks_batch_update; +pub mod spec; +pub(crate) mod summaries; +pub(crate) mod utils; diff --git a/programs/manifest/src/certora/spec/batch_update_checks.rs b/programs/manifest/src/certora/spec/batch_update_checks.rs new file mode 100644 index 000000000..573ed6b9b --- /dev/null +++ b/programs/manifest/src/certora/spec/batch_update_checks.rs @@ -0,0 +1,186 @@ +use certora::hooks::*; +use cvt::{cvt_assert, cvt_assume, cvt_vacuity_check}; +use cvt_macros::rule; +use nondet::{acc_infos_with_mem_layout, nondet}; +use state::{main_ask_order_index, main_bid_order_index, main_trader_index}; +use std::cell::RefMut; +use vectors::{cvt_no_resizable_vec, no_resizable_vec::NoResizableVec}; + +use crate::{ + program::{batch_update::*, get_mut_dynamic_account}, + state::*, + *, +}; +use hypertree::DataIndex; + +// helper to prepare a cancel order +fn prepare_cancel_order(order_sequence_number: u64) -> CancelOrderParams { + return CancelOrderParams::new(order_sequence_number); +} + +// helper to prepare a cancel order with hint +fn prepare_cancel_order_with_hint( + order_sequence_number: u64, +) -> CancelOrderParams { + // -- we assume an order is present in the main order slot + if IS_BID { + cvt_assume!(!is_bid_order_free()); + } else { + cvt_assume!(!is_ask_order_free()); + } + + let order_index: DataIndex = if IS_BID { + main_bid_order_index() + } else { + main_ask_order_index() + }; + // -- needed as an argument to get_helper_order, but is not used + let dynamic: &mut [u8; 8] = &mut [0; 8]; + let order: RestingOrder = get_helper_order(dynamic, order_index).value; + // -- make sure the order is consistent with our IS_BID and IS_GLOBAL + cvt_assume!(order.get_is_bid() == IS_BID); + + return CancelOrderParams::new_with_hint(order_sequence_number, Some(order_index)); +} + +// helper to prepare a place order +fn prepare_place_order() -> PlaceOrderParams { + // -- we assume an order is present in the main order slot + if IS_BID { + cvt_assume!(!is_bid_order_free()); + } else { + cvt_assume!(!is_ask_order_free()); + } + + // The type of order doesn't really matter because we use a mock for place order + return PlaceOrderParams::new( + nondet(), + nondet(), + nondet(), + IS_BID, + OrderType::Limit, + nondet(), + ); +} + +// Parametric rule +pub fn rule_integrity_of_batch_update_cancel() { + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let used_acc_infos: &[AccountInfo] = &acc_infos[..3]; + + // one cancel order without hint + let cancels: NoResizableVec = + cvt_no_resizable_vec!([prepare_cancel_order::(nondet())]; 10); + // no place orders + let orders: NoResizableVec = cvt_no_resizable_vec!([]; 10); + let trader_index: DataIndex = main_trader_index(); + let params: BatchUpdateParams = BatchUpdateParams::new(Some(trader_index), cancels, orders); + + let program_id: &Pubkey = &crate::id(); + // Important: by passing only three accounts, we won't have global trade accounts + process_batch_update_core(&program_id, &used_acc_infos, params).unwrap(); + + cvt_assert!(last_called_cancel_order()); + cvt_vacuity_check!(); +} + +macro_rules! get_order { + ($market_acc_info:expr, $order_index:expr) => {{ + let market_data: &mut RefMut<&mut [u8]> = + &mut $market_acc_info.try_borrow_mut_data().unwrap(); + let dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); + let order: &RestingOrder = dynamic_account.get_order_by_index($order_index); + *order + }}; +} + +// Parametric rule +pub fn rule_integrity_of_batch_update_cancel_hint() { + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let used_acc_infos: &[AccountInfo] = &acc_infos[..3]; + let market_info: &AccountInfo = &used_acc_infos[1]; + + // One cancel order with hint + let order_params: CancelOrderParams = prepare_cancel_order_with_hint::(nondet()); + cvt_assert!(order_params.order_index_hint().is_some()); + let order_index: DataIndex = order_params.order_index_hint().unwrap(); + let order: RestingOrder = get_order!(market_info, order_index); + + let cancels: NoResizableVec = cvt_no_resizable_vec!([order_params]; 10); + // No place orders + let orders: NoResizableVec = cvt_no_resizable_vec!([]; 10); + let trader_index: DataIndex = main_trader_index(); + let params = BatchUpdateParams::new(Some(trader_index), cancels, orders); + + let program_id: &Pubkey = &crate::id(); + // Important: by passing only three accounts, we won't have global trade accounts + process_batch_update_core(&program_id, &used_acc_infos, params).unwrap(); + + cvt_assert!(last_called_cancel_order_by_index()); + // Our mocks produce always aligned order indexes + cvt_assert!(order_index % (MARKET_BLOCK_SIZE as DataIndex) == 0); + // Our mocks produce always aligned trader indexes + cvt_assert!(trader_index % (MARKET_BLOCK_SIZE as DataIndex) == 0); + cvt_assert!(order.get_is_bid() == IS_BID); + cvt_assert!(order.get_trader_index() == trader_index); + + cvt_vacuity_check!(); +} + +// Parametric rule +pub fn rule_integrity_of_batch_update_place_order() { + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let used_acc_infos: &[AccountInfo] = &acc_infos[..3]; + + // no cancel orders + let cancels: NoResizableVec = cvt_no_resizable_vec!([]; 10); + // one place order + let orders: NoResizableVec = + cvt_no_resizable_vec!([prepare_place_order::()]; 10); + let trader_index: DataIndex = main_trader_index(); + let params: BatchUpdateParams = BatchUpdateParams::new(Some(trader_index), cancels, orders); + + let program_id: &Pubkey = &crate::id(); + // Important: by passing only three accounts, we won't have global trade accounts + process_batch_update_core(&program_id, &used_acc_infos, params).unwrap(); + + cvt_assert!(last_called_place_order()); + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_integrity_of_batch_update_cancel_bid() { + rule_integrity_of_batch_update_cancel::() +} + +#[rule] +#[inline(never)] +pub fn rule_integrity_of_batch_update_cancel_ask() { + rule_integrity_of_batch_update_cancel::() +} + +#[rule] +fn rule_integrity_of_batch_update_cancel_hint_bid() { + rule_integrity_of_batch_update_cancel_hint::() +} + +#[rule] +fn rule_integrity_of_batch_update_cancel_hint_ask() { + rule_integrity_of_batch_update_cancel_hint::() +} + +#[rule] +fn rule_integrity_of_batch_update_place_order_bid() { + rule_integrity_of_batch_update_place_order::() +} + +#[rule] +fn rule_integrity_of_batch_update_place_order_ask() { + rule_integrity_of_batch_update_place_order::() +} diff --git a/programs/manifest/src/certora/spec/cancel_order_checks.rs b/programs/manifest/src/certora/spec/cancel_order_checks.rs new file mode 100644 index 000000000..85aa72013 --- /dev/null +++ b/programs/manifest/src/certora/spec/cancel_order_checks.rs @@ -0,0 +1,67 @@ +use crate::*; +use cvt::{cvt_assert, cvt_assume, cvt_vacuity_check}; +use cvt_macros::rule; +use nondet::*; + +use hypertree::DataIndex; +use solana_program::account_info::AccountInfo; + +use crate::{ + program::get_mut_dynamic_account, + quantities::WrapperU64, + state::{get_helper_order, MarketRefMut, RestingOrder}, +}; + +use crate::certora::spec::no_funds_loss_util::cvt_assume_market_preconditions; + +pub fn cancel_order_by_index_no_revert() { + // IS_BID = true sets up the rule such that the order to + // be canceled is ask, and vice-versa + + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let trader: &AccountInfo = &acc_infos[0]; + let market_info: &AccountInfo = &acc_infos[1]; + let maker_trader: &AccountInfo = &acc_infos[7]; + let vault_base_token: &AccountInfo = &acc_infos[8]; + let vault_quote_token: &AccountInfo = &acc_infos[9]; + + // -- market preconditions + let maker_order_index: DataIndex = cvt_assume_market_preconditions::( + market_info, + trader, + vault_base_token, + vault_quote_token, + maker_trader, + ); + + // Assume that there will not be an overflow when adding to seat balance. + let (maker_order_base, maker_order_quote) = get_order_atoms!(maker_order_index); + let (maker_seat_base, maker_seat_quote) = get_trader_balance!(market_info, maker_trader.key); + if IS_BID { + cvt_assume!(maker_seat_base + maker_order_base.as_u64() <= u64::MAX); + } else { + cvt_assume!(maker_seat_quote + maker_order_quote.as_u64() <= u64::MAX); + } + + // -- call to cancel_order_by_index + let market_data: &mut std::cell::RefMut<&mut [u8]> = + &mut market_info.try_borrow_mut_data().unwrap(); + let mut dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); + let order_index: DataIndex = maker_order_index; + let result: ProgramResult = dynamic_account.cancel_order_by_index(order_index, &[None, None]); + cvt_assert!(result.is_ok()); + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_cancel_order_by_index_no_revert_bid() { + cancel_order_by_index_no_revert::(); +} + +#[rule] +pub fn rule_cancel_order_by_index_no_revert_ask() { + cancel_order_by_index_no_revert::(); +} diff --git a/programs/manifest/src/certora/spec/deposit_checks.rs b/programs/manifest/src/certora/spec/deposit_checks.rs new file mode 100644 index 000000000..c48b3440f --- /dev/null +++ b/programs/manifest/src/certora/spec/deposit_checks.rs @@ -0,0 +1,122 @@ +use crate::{get_trader_balance, get_trader_index}; +use cvt::{cvt_assert, cvt_assume, cvt_vacuity_check}; +use cvt_macros::rule; +use nondet::{acc_infos_with_mem_layout, nondet}; + +use crate::*; +use solana_program::account_info::AccountInfo; + +use solana_cvt::token::spl_token_account_get_amount; + +use crate::{ + program::{ + deposit::{process_deposit_core, DepositParams}, + get_mut_dynamic_account, + }, + state::{DynamicAccount, MarketRefMut}, +}; +use hypertree::DataIndex; +use state::cvt_assume_main_trader_has_seat; + +#[rule] +pub fn rule_update_balance() { + crate::certora::spec::verification_utils::init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let trader: &AccountInfo = &acc_infos[0]; + let market: &AccountInfo = &acc_infos[1]; + + cvt_assume_main_trader_has_seat(trader.key); + + let (base_atoms_old, _quote_atoms_old) = get_trader_balance!(market, &trader.key); + + let trader_index: DataIndex = get_trader_index!(market, &trader.key); + + let amount: u64 = nondet(); + + update_balance!(market, trader_index, true, true, amount); + + let (base_atoms, _quote_atoms) = get_trader_balance!(market, &trader.key); + cvt_assert!(base_atoms == base_atoms_old + amount); + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_deposit_deposits() { + use state::{cvt_assume_main_trader_has_seat, is_second_seat_taken, second_trader_pk}; + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let used_acc_infos: &[AccountInfo] = &acc_infos[..6]; + let trader: &AccountInfo = &used_acc_infos[0]; + let market: &AccountInfo = &used_acc_infos[1]; + let trader_token: &AccountInfo = &used_acc_infos[2]; + let vault_token: &AccountInfo = &used_acc_infos[3]; + + // Unrelated trader + let unrelated_trader: &AccountInfo = &acc_infos[7]; + + cvt_assume_main_trader_has_seat(trader.key); + + // -- trader and vault have different token accounts + cvt_assume!(trader_token.key != vault_token.key); + + cvt_assume!(trader.key != unrelated_trader.key); + cvt_assume!(unrelated_trader.key == second_trader_pk()); + cvt_assume!(is_second_seat_taken()); + + // Non-deterministically chosen amount + let amount: u64 = nondet(); + + // Old seat balances + let (trader_base_old, trader_quote_old) = get_trader_balance!(market, trader.key); + let (unrelated_trader_base_old, unrelated_trader_quote_old) = + get_trader_balance!(market, unrelated_trader.key); + + // Old SPL balances + let trader_amount_old = spl_token_account_get_amount(trader_token); + let vault_amount_old = spl_token_account_get_amount(vault_token); + + // Call to deposit + process_deposit_core( + &crate::id(), + &used_acc_infos, + DepositParams::new(amount, None), + ) + .unwrap(); + + // New SPL balances + let trader_amount: u64 = spl_token_account_get_amount(trader_token); + let vault_amount: u64 = spl_token_account_get_amount(vault_token); + + // Difference in SPL balances + cvt_assert!(trader_amount_old >= trader_amount); + cvt_assert!(vault_amount >= vault_amount_old); + let trader_diff: u64 = trader_amount_old - trader_amount; + let vault_diff: u64 = vault_amount - vault_amount_old; + + // Diffs must equal the amount + cvt_assert!(trader_diff == amount); + cvt_assert!(vault_diff == amount); + + // New seat balances + let (trader_base, trader_quote) = get_trader_balance!(market, trader.key); + let (unrelated_trader_base, unrelated_trader_quote) = + get_trader_balance!(market, unrelated_trader.key); + + // Diffs in base/quote seat balance + let trader_base_diff: u64 = trader_base - trader_base_old; + let trader_quote_diff: u64 = trader_quote - trader_quote_old; + + // One of the diffs should be amount, the other zero + cvt_assert!(trader_base_diff + trader_quote_diff == amount); + cvt_assert!(trader_base_diff == 0 || trader_quote_diff == 0); + + // The balances of an unrelated trader are not changed + cvt_assert!( + unrelated_trader_base == unrelated_trader_base_old + && unrelated_trader_quote == unrelated_trader_quote_old + ); + + cvt_vacuity_check!(); +} diff --git a/programs/manifest/src/certora/spec/funds_checks.rs b/programs/manifest/src/certora/spec/funds_checks.rs new file mode 100644 index 000000000..5e6092243 --- /dev/null +++ b/programs/manifest/src/certora/spec/funds_checks.rs @@ -0,0 +1,464 @@ +use crate::*; +use cvt::{cvt_assume, cvt_vacuity_check}; +use cvt_macros::rule; +use nondet::*; + +use solana_program::account_info::AccountInfo; + +use crate::{ + program::{ + deposit::{process_deposit_core, DepositParams}, + get_mut_dynamic_account, + withdraw::{process_withdraw_core, WithdrawParams}, + }, + quantities::{BaseAtoms, QuoteAtoms}, + state::{get_helper_order, AddOrderToMarketArgs, DynamicAccount, MarketRefMut, RestingOrder}, +}; +use hypertree::DataIndex; + +use crate::certora::spec::no_funds_loss_util::*; +use state::{main_trader_index, second_trader_index}; + +fn rule_deposit_check() { + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let used_acc_infos: &[AccountInfo] = &acc_infos[..6]; + let trader: &AccountInfo = &used_acc_infos[0]; + let market_info: &AccountInfo = &used_acc_infos[1]; + let trader_token: &AccountInfo = &used_acc_infos[2]; + let vault_token: &AccountInfo = &used_acc_infos[3]; + + let maker_trader: &AccountInfo = &acc_infos[7]; + let vault_base_token: &AccountInfo = &acc_infos[8]; + let vault_quote_token: &AccountInfo = &acc_infos[9]; + + // -- market preconditions + // the parameter true below implies there is a bid order, + // but the rule_deposit_check does not consider any orders + let maker_order_index: DataIndex = cvt_assume_market_preconditions::( + market_info, + trader, + vault_base_token, + vault_quote_token, + maker_trader, + ); + // -- additional precondition for deposit + let market_base_vault_pk: Pubkey = get_base_vault!(market_info); + let market_quote_vault_pk: Pubkey = get_quote_vault!(market_info); + // -- vault_token is either eqauls base vault or quote vault + let vault_pk: Pubkey = if IS_BASE { + market_base_vault_pk + } else { + market_quote_vault_pk + }; + cvt_assume!(vault_token.key == &vault_pk); + // -- trader and vault have different token accounts + cvt_assume!(trader_token.key != vault_token.key); + + // if IS_BASE, then vault_base amount comes from vault_token + // otherwise, vault_quote amount comes from vault_token + let balances_old: AllBalances = if IS_BASE { + record_all_balances( + market_info, + vault_token, + vault_quote_token, + trader, + maker_trader, + maker_order_index, + ) + } else { + record_all_balances( + market_info, + vault_base_token, + vault_token, + trader, + maker_trader, + maker_order_index, + ) + }; + + // -- assume no loss of funds invariant + cvt_assume_funds_invariants(balances_old); + + // -- atempt to deposit an arbitrary amount + let amount_arg: u64 = nondet(); + process_deposit_core( + &crate::id(), + &used_acc_infos, + DepositParams::new(amount_arg, None), + ) + .unwrap(); + + let balances_new: AllBalances = if IS_BASE { + record_all_balances( + market_info, + vault_token, + vault_quote_token, + trader, + maker_trader, + maker_order_index, + ) + } else { + record_all_balances( + market_info, + vault_base_token, + vault_token, + trader, + maker_trader, + maker_order_index, + ) + }; + + // -- assert no loss of funds invariant + cvt_assert_funds_invariants(balances_new); + + // -- additional properties + cvt_assert_deposit_extra::(balances_old, balances_new, amount_arg); + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_deposit_base() { + rule_deposit_check::(); +} + +#[rule] +pub fn rule_deposit_quote() { + rule_deposit_check::(); +} + +fn rule_withdraw_check() { + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let used_acc_infos: &[AccountInfo] = &acc_infos[..6]; + let trader: &AccountInfo = &used_acc_infos[0]; + let market_info: &AccountInfo = &used_acc_infos[1]; + let trader_token: &AccountInfo = &used_acc_infos[2]; + let vault_token: &AccountInfo = &used_acc_infos[3]; + + let maker_trader: &AccountInfo = &acc_infos[7]; + let vault_base_token: &AccountInfo = &acc_infos[8]; + let vault_quote_token: &AccountInfo = &acc_infos[9]; + + // -- market preconditions + // the parameter true below implies there is a bid order, + // but the rule_withdraw_check does not consider any orders + let maker_order_index: DataIndex = cvt_assume_market_preconditions::( + market_info, + trader, + vault_base_token, + vault_quote_token, + maker_trader, + ); + // -- additional precondition for deposit + let market_base_vault_pk: Pubkey = get_base_vault!(market_info); + let market_quote_vault_pk: Pubkey = get_quote_vault!(market_info); + // -- vault_token is either eqauls base vault or quote vault + let vault_pk: Pubkey = if IS_BASE { + market_base_vault_pk + } else { + market_quote_vault_pk + }; + cvt_assume!(vault_token.key == &vault_pk); + // -- trader and vault have different token accounts + cvt_assume!(trader_token.key != vault_token.key); + + // if IS_BASE, then vault_base amount comes from vault_token + // otherwise, vault_quote amount comes from vault_token + let balances_old: AllBalances = if IS_BASE { + record_all_balances( + market_info, + vault_token, + vault_quote_token, + trader, + maker_trader, + maker_order_index, + ) + } else { + record_all_balances( + market_info, + vault_base_token, + vault_token, + trader, + maker_trader, + maker_order_index, + ) + }; + + // -- assume no loss of funds invariant + cvt_assume_funds_invariants(balances_old); + + // -- atempt to withdraw an arbitrary amount + let amount_arg: u64 = nondet(); + process_withdraw_core( + &crate::id(), + &used_acc_infos, + WithdrawParams::new(amount_arg, None), + ) + .unwrap(); + + let balances_new: AllBalances = if IS_BASE { + record_all_balances( + market_info, + vault_token, + vault_quote_token, + trader, + maker_trader, + maker_order_index, + ) + } else { + record_all_balances( + market_info, + vault_base_token, + vault_token, + trader, + maker_trader, + maker_order_index, + ) + }; + + // -- assert no loss of funds invariant + cvt_assert_funds_invariants(balances_new); + + // -- additional properties + cvt_assert_withdraw_extra::(balances_old, balances_new, amount_arg); + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_withdraw_base() { + rule_withdraw_check::(); +} + +#[rule] +pub fn rule_withdraw_quote() { + rule_withdraw_check::(); +} + +fn rest_remaining_check() { + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let trader: &AccountInfo = &acc_infos[0]; + let market_info: &AccountInfo = &acc_infos[1]; + let maker_trader: &AccountInfo = &acc_infos[7]; + let vault_base_token: &AccountInfo = &acc_infos[8]; + let vault_quote_token: &AccountInfo = &acc_infos[9]; + + // -- market preconditions + let maker_order_index: DataIndex = cvt_assume_market_preconditions::( + market_info, + trader, + vault_base_token, + vault_quote_token, + maker_trader, + ); + + let balances_old: AllBalances = record_all_balances( + market_info, + vault_base_token, + vault_quote_token, + trader, + maker_trader, + maker_order_index, + ); + + // -- assume no loss of funds invariant + cvt_assume_funds_invariants(balances_old); + + let args: AddOrderToMarketArgs = AddOrderToMarketArgs { + market: *market_info.key, + trader_index: main_trader_index(), + num_base_atoms: nondet(), + price: nondet(), + is_bid: IS_BID, + last_valid_slot: nondet(), + order_type: state::OrderType::Limit, + global_trade_accounts_opts: &[None, None], + current_slot: Some(nondet()), + }; + + let remaining_base_atoms_arg: BaseAtoms = nondet(); + let order_sequence_number_arg: u64 = nondet(); + let total_base_atoms_traded_arg: BaseAtoms = nondet(); + let total_quote_atoms_traded_arg: QuoteAtoms = nondet(); + + rest_remaining!( + market_info, + args, + remaining_base_atoms_arg, + order_sequence_number_arg, + total_base_atoms_traded_arg, + total_quote_atoms_traded_arg + ); + + let balances_new: AllBalances = record_all_balances( + market_info, + vault_base_token, + vault_quote_token, + trader, + maker_trader, + maker_order_index, + ); + + // -- assert no loss of funds invariant + cvt_assert_funds_invariants(balances_new); + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_rest_remaining_ask() { + rest_remaining_check::(); +} + +#[rule] +pub fn rule_rest_remaining_bid() { + rest_remaining_check::(); +} + +pub fn cancel_order_by_index_check() { + // IS_BID = true sets up the rule such that the order to + // be canceled is ask, and vice-versa + + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let trader: &AccountInfo = &acc_infos[0]; + let market_info: &AccountInfo = &acc_infos[1]; + let maker_trader: &AccountInfo = &acc_infos[2]; + let vault_base_token: &AccountInfo = &acc_infos[3]; + let vault_quote_token: &AccountInfo = &acc_infos[4]; + + // -- market preconditions + let maker_order_index: DataIndex = cvt_assume_market_preconditions::( + market_info, + trader, + vault_base_token, + vault_quote_token, + maker_trader, + ); + + let balances_old: AllBalances = record_all_balances( + market_info, + vault_base_token, + vault_quote_token, + trader, + maker_trader, + maker_order_index, + ); + + // -- assume no loss of funds invariant + cvt_assume_funds_invariants(balances_old); + + // -- call to cancel_order_by_index + let order_index: DataIndex = maker_order_index; + cancel_order_by_index!(market_info, order_index); + + let balances_new: AllBalances = record_all_balances( + market_info, + vault_base_token, + vault_quote_token, + trader, + maker_trader, + maker_order_index, + ); + + // -- assert no loss of funds invariant + cvt_assert_funds_invariants(balances_new); + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_cancel_order_by_index_ask() { + // IS_BID = true cancels an ask order + cancel_order_by_index_check::(); +} + +#[rule] +pub fn rule_cancel_order_by_index_bid() { + // IS_BID = false cancels a bid order + cancel_order_by_index_check::(); +} + +pub fn cancel_order_check() { + // IS_BID = true sets up the rule such that the order to + // be canceled is ask, and vice-versa + + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let trader: &AccountInfo = &acc_infos[0]; + let market_info: &AccountInfo = &acc_infos[1]; + let maker_trader: &AccountInfo = &acc_infos[2]; + let vault_base_token: &AccountInfo = &acc_infos[3]; + let vault_quote_token: &AccountInfo = &acc_infos[4]; + + // -- market preconditions + let maker_order_index: DataIndex = cvt_assume_market_preconditions::( + market_info, + trader, + vault_base_token, + vault_quote_token, + maker_trader, + ); + + let balances_old: AllBalances = record_all_balances( + market_info, + vault_base_token, + vault_quote_token, + trader, + maker_trader, + maker_order_index, + ); + + // -- assume no loss of funds invariant + cvt_assume_funds_invariants(balances_old); + + // -- call to cancel_order_by_index + let trader_index: DataIndex = second_trader_index(); + let order_index: DataIndex = maker_order_index; + // -- needed as an argument to get_helper_order, but is not used + let dynamic: &mut [u8; 8] = &mut [0; 8]; + let resting_order: &RestingOrder = get_helper_order(dynamic, order_index).get_value(); + let order_sequence_number: u64 = resting_order.get_sequence_number(); + { + let market_data: &mut std::cell::RefMut<&mut [u8]> = + &mut market_info.try_borrow_mut_data().unwrap(); + let mut dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); + dynamic_account + .cancel_order(trader_index, order_sequence_number, &[None, None]) + .unwrap(); + }; + + let balances_new: AllBalances = record_all_balances( + market_info, + vault_base_token, + vault_quote_token, + trader, + maker_trader, + maker_order_index, + ); + + // -- assert no loss of funds invariant + cvt_assert_funds_invariants(balances_new); + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_cancel_order_ask() { + // IS_BID = true cancels an ask order + cancel_order_check::(); +} + +#[rule] +pub fn rule_cancel_order_bid() { + // IS_BID = false cancels a bid order + cancel_order_check::(); +} diff --git a/programs/manifest/src/certora/spec/market_checks.rs b/programs/manifest/src/certora/spec/market_checks.rs new file mode 100644 index 000000000..eab68606f --- /dev/null +++ b/programs/manifest/src/certora/spec/market_checks.rs @@ -0,0 +1,150 @@ +use super::verification_utils::init_static; +use crate::{ + claim_seat, create_empty_market, cvt_assert_is_nil, deposit, get_trader_balance, + get_trader_index, +}; +use calltrace::cvt_cex_print_u64; +use cvt::{cvt_assert, cvt_assume, cvt_vacuity_check}; +use cvt_macros::rule; +use nondet::nondet; + +use crate::{ + program::get_mut_dynamic_account, + state::{ + is_main_seat_free, is_main_seat_taken, is_second_seat_free, main_trader_index, + main_trader_pk, second_trader_index, second_trader_pk, MarketFixed, MarketRefMut, + }, +}; +use hypertree::{get_mut_helper, is_nil, DataIndex, NIL}; +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; + +#[rule] +pub fn rule_market_empty() { + init_static(); + + let market_info: AccountInfo = nondet(); + + // Create an empty market + create_empty_market!(market_info); + + let trader_key: Pubkey = *main_trader_pk(); + cvt_assume!(is_main_seat_free()); + + cvt_assert_is_nil!(get_trader_index!(market_info, &trader_key)); + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_market_claim_seat_once() { + init_static(); + + let market_info: AccountInfo = nondet(); + + // Create an empty market + create_empty_market!(market_info); + + let trader_key: Pubkey = *main_trader_pk(); + cvt_assume!(is_main_seat_free()); + + cvt_assert_is_nil!(get_trader_index!(market_info, &trader_key)); + claim_seat!(market_info, &trader_key); + cvt_assert!(get_trader_index!(market_info, &trader_key) == main_trader_index()); + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_market_claim_seat_twice_same_trader() { + init_static(); + + let market_info: AccountInfo = nondet(); + + // Create an empty market + create_empty_market!(market_info); + + let trader_key: Pubkey = *main_trader_pk(); + cvt_assume!(is_main_seat_free()); + cvt_assume!(is_second_seat_free()); + + cvt_assert_is_nil!(get_trader_index!(market_info, &trader_key)); + claim_seat!(market_info, &trader_key); + cvt_assert!(get_trader_index!(market_info, &trader_key) == main_trader_index()); + + // second call to claim_seat will make the rule pass vacuously + claim_seat!(market_info, &trader_key); + cvt_assert!(get_trader_index!(market_info, &trader_key) == main_trader_index()); + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_market_claim_seat_twice_different_trader() { + init_static(); + + let market_info: AccountInfo = nondet(); + + // Create an empty market + create_empty_market!(market_info); + + let trader1_key: Pubkey = *main_trader_pk(); + cvt_assume!(is_main_seat_free()); + + let trader2_key: Pubkey = *second_trader_pk(); + cvt_assume!(is_second_seat_free()); + cvt_assume!(trader2_key != trader1_key); + + cvt_assert_is_nil!(get_trader_index!(market_info, &trader1_key)); + claim_seat!(market_info, &trader1_key); + cvt_assert!(get_trader_index!(market_info, &trader1_key) == main_trader_index()); + + cvt_assert_is_nil!(get_trader_index!(market_info, &trader2_key)); + claim_seat!(market_info, &trader2_key); + cvt_assert!(get_trader_index!(market_info, &trader2_key) == second_trader_index()); + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_market_deposit() { + init_static(); + + let market_info: AccountInfo = nondet(); + + // Create an empty market + create_empty_market!(market_info); + + let trader_key: Pubkey = *main_trader_pk(); + cvt_assume!(is_main_seat_free()); + + cvt_assert_is_nil!(get_trader_index!(market_info, &trader_key)); + claim_seat!(market_info, &trader_key); + let trader_index: DataIndex = get_trader_index!(market_info, &trader_key); + cvt_assert!(trader_index == main_trader_index()); + + deposit!(market_info, trader_index, 100, true); + let (base_atoms, quote_atoms) = get_trader_balance!(market_info, &trader_key); + cvt_cex_print_u64!(1, u64::from(base_atoms), u64::from(quote_atoms)); + cvt_assert!(u64::from(base_atoms) == 100); + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_market_release_seat() { + init_static(); + + let market_info: AccountInfo = nondet(); + + // Create an empty market + create_empty_market!(market_info); + + let trader_key: Pubkey = *main_trader_pk(); + cvt_assume!(is_main_seat_taken()); + + { + let market_data: &mut std::cell::RefMut<&mut [u8]> = + &mut market_info.try_borrow_mut_data().unwrap(); + let mut dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); + dynamic_account.release_seat(&trader_key).unwrap(); + } + + cvt_assert!(is_main_seat_free()); + cvt_vacuity_check!(); +} diff --git a/programs/manifest/src/certora/spec/matching_checks.rs b/programs/manifest/src/certora/spec/matching_checks.rs new file mode 100644 index 000000000..7628645d8 --- /dev/null +++ b/programs/manifest/src/certora/spec/matching_checks.rs @@ -0,0 +1,474 @@ +use crate::*; +use cvt::{cvt_assert, cvt_assume, cvt_vacuity_check}; +use cvt_macros::rule; +use nondet::*; + +use certora::hooks::last_called_remove_order_from_tree_and_free; +use solana_program::account_info::AccountInfo; + +use certora::spec::place_order_checks::place_single_order_nondet_inputs; +use state::get_helper_order; + +use crate::{ + certora::spec::no_funds_loss_util::*, + program::get_mut_dynamic_account, + quantities::{BaseAtoms, QuoteAtoms, QuoteAtomsPerBaseAtom, WrapperU64}, + state::{ + market::market_helpers::{AddOrderStatus, AddOrderToMarketInnerResult, AddSingleOrderCtx}, + DynamicAccount, MarketRefMut, RestingOrder, + }, +}; +use hypertree::DataIndex; + +// Rules to check if a matching order exists in the book, +// then matching will happen +pub fn matching_if_maker_order_exists() { + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let trader = &acc_infos[0]; + let market_info = &acc_infos[1]; + + let maker_trader = &acc_infos[7]; + let vault_base_token = &acc_infos[8]; + let vault_quote_token = &acc_infos[9]; + + // -- market preconditions + let maker_order_index: DataIndex = cvt_assume_market_preconditions::( + market_info, + trader, + vault_base_token, + vault_quote_token, + maker_trader, + ); + + let (args, remaining_base_atoms, now_slot) = + place_single_order_nondet_inputs::(market_info); + + // -- assumptions that maker_order is not expired and it matches on price + let dynamic: &mut [u8; 8] = &mut [0; 8]; + let maker_order: &RestingOrder = get_helper_order(dynamic, maker_order_index).get_value(); + let maker_order_price = maker_order.get_price(); + // -- maker_order is not expired + cvt_assume!(!maker_order.is_expired(now_slot)); + // -- maker_order matches on price + if IS_BID { + cvt_assume!(maker_order_price <= args.price); + } else { + cvt_assume!(maker_order_price >= args.price); + } + + // -- call to place_single_order + let (res, _total_base_atoms_traded, _total_quote_atoms_traded) = place_single_order!( + market_info, + args, + remaining_base_atoms, + now_slot, + maker_order_index + ); + + // -- assert to make sure that the order matched partially or fully + cvt_assert!(res.status == AddOrderStatus::PartialFill || res.status == AddOrderStatus::Filled); + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_matching_if_maker_order_exists_bid() { + matching_if_maker_order_exists::(); +} + +#[rule] +pub fn rule_matching_if_maker_order_exists_ask() { + matching_if_maker_order_exists::(); +} + +// Rules to check if matching happened, then prices must have crossed +pub fn crossed_prices_if_matched() { + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let trader = &acc_infos[0]; + let market_info = &acc_infos[1]; + + let maker_trader = &acc_infos[7]; + let vault_base_token = &acc_infos[8]; + let vault_quote_token = &acc_infos[9]; + + // -- market preconditions + let maker_order_index: DataIndex = cvt_assume_market_preconditions::( + market_info, + trader, + vault_base_token, + vault_quote_token, + maker_trader, + ); + + let (args, remaining_base_atoms, now_slot) = + place_single_order_nondet_inputs::(market_info); + + // -- get maker_order price + let dynamic: &mut [u8; 8] = &mut [0; 8]; + let maker_order: &RestingOrder = get_helper_order(dynamic, maker_order_index).get_value(); + let maker_order_price = maker_order.get_price(); + let args_price = args.price; + + // -- call to place_single_order + let (res, _total_base_atoms_traded, _total_quote_atoms_traded) = place_single_order!( + market_info, + args, + remaining_base_atoms, + now_slot, + maker_order_index + ); + // -- assume that the order matched partially or fully + cvt_assume!(res.status == AddOrderStatus::PartialFill || res.status == AddOrderStatus::Filled); + + // -- assert to check that the orders must have matched on price + if IS_BID { + cvt_assert!(maker_order_price <= args_price); + } else { + cvt_assert!(maker_order_price >= args_price); + } + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_crossed_prices_if_matched_bid() { + crossed_prices_if_matched::(); +} + +#[rule] +pub fn rule_crossed_prices_if_matched_ask() { + crossed_prices_if_matched::(); +} + +// Rules to verify that trader balances are modified as expected in the fully_matched case +pub fn place_single_order_full_match_balances() { + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let trader = &acc_infos[0]; + let market_info = &acc_infos[1]; + + let maker_trader = &acc_infos[7]; + let vault_base_token = &acc_infos[8]; + let vault_quote_token = &acc_infos[9]; + + // -- market assumptions + + let maker_order_index: DataIndex = cvt_assume_market_preconditions::( + market_info, + trader, + vault_base_token, + vault_quote_token, + maker_trader, + ); + + // -- record trader balances before place_single_order + let (trader_base_old, trader_quote_old) = get_trader_balance!(market_info, trader.key); + let (maker_trader_base_old, maker_trader_quote_old) = + get_trader_balance!(market_info, maker_trader.key); + + // -- compute base_atoms_traded and quote_atoms_traded + let dynamic = [0u8; 8]; + let maker_order = get_helper_order(&dynamic, maker_order_index).get_value(); + let base_atoms_traded: BaseAtoms = maker_order.get_num_base_atoms(); + let matched_price: QuoteAtomsPerBaseAtom = maker_order.get_price(); + let quote_atoms_traded: QuoteAtoms = matched_price + .checked_quote_for_base(base_atoms_traded, IS_BID != true) + .unwrap(); + + let (args, remaining_base_atoms, now_slot) = + place_single_order_nondet_inputs::(market_info); + + // -- call to place_single_order + let (res, _total_base_atoms_traded, _total_quote_atoms_traded) = place_single_order!( + market_info, + args, + remaining_base_atoms, + now_slot, + maker_order_index + ); + cvt_assume!(res.status == AddOrderStatus::Filled); + + // -- record trader balances after place_single_order + let (trader_base_new, trader_quote_new) = get_trader_balance!(market_info, trader.key); + let (maker_trader_base_new, maker_trader_quote_new) = + get_trader_balance!(market_info, maker_trader.key); + + // -- asserts to establish that trader balances changed as expected + if IS_BID { + cvt_assert!(trader_base_new == trader_base_old + base_atoms_traded.as_u64()); + cvt_assert!(trader_quote_new == trader_quote_old - quote_atoms_traded.as_u64()); + cvt_assert!(maker_trader_base_new == maker_trader_base_old); + cvt_assert!(maker_trader_quote_new == maker_trader_quote_old + quote_atoms_traded.as_u64()); + } else { + cvt_assert!(trader_base_new == trader_base_old - base_atoms_traded.as_u64()); + cvt_assert!(trader_quote_new == trader_quote_old + quote_atoms_traded.as_u64()); + cvt_assert!(maker_trader_base_new == maker_trader_base_old + base_atoms_traded.as_u64()); + cvt_assert!(maker_trader_quote_new == maker_trader_quote_old); + } + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_place_single_order_full_match_balances_bid() { + place_single_order_full_match_balances::(); +} + +#[rule] +pub fn rule_place_single_order_full_match_balances_ask() { + place_single_order_full_match_balances::(); +} + +// Rules to verify that trader balances are modified as expected in the partially_matched case +pub fn place_single_order_partial_match_balances() { + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let trader = &acc_infos[0]; + let market_info = &acc_infos[1]; + + let maker_trader = &acc_infos[7]; + let vault_base_token = &acc_infos[8]; + let vault_quote_token = &acc_infos[9]; + + // -- market assumptions + + let maker_order_index: DataIndex = cvt_assume_market_preconditions::( + market_info, + trader, + vault_base_token, + vault_quote_token, + maker_trader, + ); + + // -- record trader balances before place_single_order + let (trader_base_old, trader_quote_old) = get_trader_balance!(market_info, trader.key); + let (maker_trader_base_old, maker_trader_quote_old) = + get_trader_balance!(market_info, maker_trader.key); + + let (args, remaining_base_atoms, now_slot) = + place_single_order_nondet_inputs::(market_info); + + // -- compute base_atoms_traded and quote_atoms_traded + let dynamic = [0u8; 8]; + let maker_order = get_helper_order(&dynamic, maker_order_index).get_value(); + let base_atoms_traded: BaseAtoms = remaining_base_atoms; + let matched_price: QuoteAtomsPerBaseAtom = maker_order.get_price(); + let quote_atoms_traded: QuoteAtoms = matched_price + .checked_quote_for_base(base_atoms_traded, IS_BID != false) + .unwrap(); + + // -- call to place_single_order + let (res, _total_base_atoms_traded, _total_quote_atoms_traded) = place_single_order!( + market_info, + args, + remaining_base_atoms, + now_slot, + maker_order_index + ); + cvt_assume!(res.status == AddOrderStatus::PartialFill); + + // -- record trader balances after place_single_order + let (trader_base_new, trader_quote_new) = get_trader_balance!(market_info, trader.key); + let (maker_trader_base_new, maker_trader_quote_new) = + get_trader_balance!(market_info, maker_trader.key); + + // -- asserts to establish that trader balances changed as expected + if IS_BID { + cvt_assert!(trader_base_new == trader_base_old + base_atoms_traded.as_u64()); + cvt_assert!(trader_quote_new == trader_quote_old - quote_atoms_traded.as_u64()); + cvt_assert!(maker_trader_base_new == maker_trader_base_old); + cvt_assert!(maker_trader_quote_new == maker_trader_quote_old + quote_atoms_traded.as_u64()); + } else { + cvt_assert!(trader_base_new == trader_base_old - base_atoms_traded.as_u64()); + cvt_assert!(trader_quote_new == trader_quote_old + quote_atoms_traded.as_u64()); + cvt_assert!(maker_trader_base_new == maker_trader_base_old + base_atoms_traded.as_u64()); + + // maker may receive a bonus quote atom due to rounding + cvt_assert!(maker_trader_quote_old <= maker_trader_quote_new); + cvt_assert!(maker_trader_quote_new <= maker_trader_quote_old + 1); + } + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_place_single_order_partial_match_balances_bid() { + place_single_order_partial_match_balances::(); +} + +#[rule] +pub fn rule_place_single_order_partial_match_balances_ask() { + place_single_order_partial_match_balances::(); +} + +// Rules to check if the maker_order is fully matched, then it is removed +pub fn matching_order_removed_if_fully_matched() { + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let trader = &acc_infos[0]; + let market_info = &acc_infos[1]; + + let maker_trader = &acc_infos[7]; + let vault_base_token = &acc_infos[8]; + let vault_quote_token = &acc_infos[9]; + + // -- market assumptions + + let maker_order_index: DataIndex = cvt_assume_market_preconditions::( + market_info, + trader, + vault_base_token, + vault_quote_token, + maker_trader, + ); + + let (args, remaining_base_atoms, now_slot) = + place_single_order_nondet_inputs::(market_info); + + // -- call to place_single_order + let (res, _total_base_atoms_traded, _total_quote_atoms_traded) = place_single_order!( + market_info, + args, + remaining_base_atoms, + now_slot, + maker_order_index + ); + cvt_assume!(res.status == AddOrderStatus::Filled); + + // -- assert that remove_order_from_tree_and_free was called + cvt_assert!(last_called_remove_order_from_tree_and_free()); + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_matching_order_removed_if_fully_matched_bid() { + matching_order_removed_if_fully_matched::(); +} + +#[rule] +pub fn rule_matching_order_removed_if_fully_matched_ask() { + matching_order_removed_if_fully_matched::(); +} + +// Rules to check if the maker_order was (i) not expired (ii) matched on price removed +// and (iii) removed from the tree; then it must have been fully_matched +pub fn matching_fully_matched_if_order_removed() { + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let trader = &acc_infos[0]; + let market_info = &acc_infos[1]; + + let maker_trader = &acc_infos[7]; + let vault_base_token = &acc_infos[8]; + let vault_quote_token = &acc_infos[9]; + + // -- market assumptions + + let maker_order_index: DataIndex = cvt_assume_market_preconditions::( + market_info, + trader, + vault_base_token, + vault_quote_token, + maker_trader, + ); + + let (args, remaining_base_atoms, now_slot) = + place_single_order_nondet_inputs::(market_info); + + // -- assumptions that maker_order is not expired and it matches on price + let dynamic: &mut [u8; 8] = &mut [0; 8]; + let maker_order: &RestingOrder = get_helper_order(dynamic, maker_order_index).get_value(); + // -- maker_order is not expired + cvt_assume!(!maker_order.is_expired(now_slot)); + + // -- call to place_single_order + let (res, _total_base_atoms_traded, _total_quote_atoms_traded) = place_single_order!( + market_info, + args, + remaining_base_atoms, + now_slot, + maker_order_index + ); + + // -- assume that remove_order_from_tree_and_free was called + cvt_assume!(last_called_remove_order_from_tree_and_free()); + + // -- assert that maker_order must have been fully matched + cvt_assert!(res.status == AddOrderStatus::Filled); + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_matching_fully_matched_if_order_removed_bid() { + matching_fully_matched_if_order_removed::(); +} + +#[rule] +pub fn rule_matching_fully_matched_if_order_removed_ask() { + matching_fully_matched_if_order_removed::(); +} + +// Rules to check that the atoms contained in maker_order cannot increase after matching +pub fn matching_decrease_maker_order_atoms() { + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let trader = &acc_infos[0]; + let market_info = &acc_infos[1]; + + let maker_trader = &acc_infos[7]; + let vault_base_token = &acc_infos[8]; + let vault_quote_token = &acc_infos[9]; + + // -- market assumptions + + let maker_order_index: DataIndex = cvt_assume_market_preconditions::( + market_info, + trader, + vault_base_token, + vault_quote_token, + maker_trader, + ); + + let (maker_order_base_old, maker_order_quote_old) = get_order_atoms!(maker_order_index); + + let (args, remaining_base_atoms, now_slot) = + place_single_order_nondet_inputs::(market_info); + + // -- call to place_single_order + let (_res, _total_base_atoms_traded, _total_quote_atoms_traded) = place_single_order!( + market_info, + args, + remaining_base_atoms, + now_slot, + maker_order_index + ); + + let (maker_order_base_new, maker_order_quote_new) = get_order_atoms!(maker_order_index); + + // -- assert that maker_order atoms cannot increase + cvt_assert!(maker_order_base_new.as_u64() <= maker_order_base_old.as_u64()); + cvt_assert!(maker_order_quote_new.as_u64() <= maker_order_quote_old.as_u64()); + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_matching_decrease_maker_order_atoms_bid() { + matching_decrease_maker_order_atoms::(); +} + +#[rule] +pub fn rule_matching_decrease_maker_order_atoms_ask() { + matching_decrease_maker_order_atoms::(); +} diff --git a/programs/manifest/src/certora/spec/mod.rs b/programs/manifest/src/certora/spec/mod.rs new file mode 100644 index 000000000..ce2140580 --- /dev/null +++ b/programs/manifest/src/certora/spec/mod.rs @@ -0,0 +1,31 @@ +pub mod batch_update_checks; +pub mod cancel_order_checks; +pub mod deposit_checks; +pub mod funds_checks; +pub mod market_checks; +pub mod matching_checks; +pub mod no_funds_loss_util; +pub mod place_order_checks; +pub mod rbtree_checks; +pub mod swap_checks; +pub mod withdraw_checks; + +/// Utility functions for verification. +pub(crate) mod verification_utils { + /// Initialises the static (i.e., global) mutable variables to the initial + /// value that they have in the system. This function should be called from + /// rules that assume to start from a freshly instantiated system. If this + /// function is not called, the prover will assume that static mutable + /// variables have `nondet` value. + pub(crate) fn init_static() { + crate::state::init_mock(); + crate::certora::hooks::initialize_hooks(); + } +} + +#[macro_export] +macro_rules! cvt_static_initializer { + () => { + crate::certora::spec::verification_utils::init_static(); + }; +} diff --git a/programs/manifest/src/certora/spec/no_funds_loss_util.rs b/programs/manifest/src/certora/spec/no_funds_loss_util.rs new file mode 100644 index 000000000..5a5349e0c --- /dev/null +++ b/programs/manifest/src/certora/spec/no_funds_loss_util.rs @@ -0,0 +1,624 @@ +use cvt::{cvt_assert, cvt_assume}; +use nondet::*; + +use crate::{ + program::get_mut_dynamic_account, + quantities::{BaseAtoms, QuoteAtoms, QuoteAtomsPerBaseAtom, WrapperU64}, + state::{ + cvt_assume_second_trader_has_seat, get_helper_order, is_ask_order_free, is_bid_order_free, + second_trader_index, DynamicAccount, MarketRefMut, RestingOrder, + }, + *, +}; +use hypertree::DataIndex; +use solana_cvt::token::spl_token_account_get_amount; +use solana_program::account_info::AccountInfo; +use state::{ + is_ask_order_taken, is_bid_order_taken, main_ask_order_index, main_bid_order_index, OrderType, +}; + +#[derive(Clone, Copy)] +pub struct AllBalances { + pub vault_base: u64, + pub vault_quote: u64, + pub withdrawable_base: u64, + pub orderbook_base: u64, + pub withdrawable_quote: u64, + pub orderbook_quote: u64, + pub trader_base: u64, + pub trader_quote: u64, + pub maker_trader_base: u64, + pub maker_trader_quote: u64, + pub maker_order_base: u64, + pub maker_order_quote: u64, +} + +impl AllBalances { + pub fn new( + vault_base: u64, + vault_quote: u64, + withdrawable_base: u64, + orderbook_base: u64, + withdrawable_quote: u64, + orderbook_quote: u64, + trader_base: u64, + trader_quote: u64, + maker_trader_base: u64, + maker_trader_quote: u64, + maker_order_base: u64, + maker_order_quote: u64, + ) -> Self { + Self { + vault_base, + vault_quote, + withdrawable_base, + orderbook_base, + withdrawable_quote, + orderbook_quote, + trader_base, + trader_quote, + maker_trader_base, + maker_trader_quote, + maker_order_base, + maker_order_quote, + } + } +} + +/// Extract all relevant balances from all accounts +pub fn record_all_balances_without_order( + market: &AccountInfo, + vault_base_token: &AccountInfo, + vault_quote_token: &AccountInfo, + trader: &AccountInfo, + maker_trader: &AccountInfo, +) -> AllBalances { + let (trader_base, trader_quote) = get_trader_balance!(market, trader.key); + let (maker_trader_base, maker_trader_quote) = get_trader_balance!(market, maker_trader.key); + + let withdrawable_base: u64 = get_withdrawable_base_atoms!(market); + let withdrawable_quote: u64 = get_withdrawable_quote_atoms!(market); + + let orderbook_base: u64 = get_orderbook_base_atoms!(market); + let orderbook_quote: u64 = get_orderbook_quote_atoms!(market); + + let vault_base: u64 = spl_token_account_get_amount(vault_base_token); + let vault_quote: u64 = spl_token_account_get_amount(vault_quote_token); + + AllBalances::new( + vault_base, + vault_quote, + withdrawable_base, + orderbook_base, + withdrawable_quote, + orderbook_quote, + trader_base, + trader_quote, + maker_trader_base, + maker_trader_quote, + 0, + 0, + ) +} + +/// Extract all relevant balances from all accounts and a maker order +pub fn record_all_balances( + market: &AccountInfo, + vault_base_token: &AccountInfo, + vault_quote_token: &AccountInfo, + trader: &AccountInfo, + maker_trader: &AccountInfo, + maker_order_index: DataIndex, +) -> AllBalances { + let mut all_balances = record_all_balances_without_order( + market, + vault_base_token, + vault_quote_token, + trader, + maker_trader, + ); + let (maker_order_base, maker_order_quote) = get_order_atoms!(maker_order_index); + all_balances.maker_order_base = maker_order_base.as_u64(); + all_balances.maker_order_quote = maker_order_quote.as_u64(); + all_balances +} + +// Very basic market pre-conditions +pub fn cvt_assume_basic_market_preconditions( + market: &AccountInfo, + trader: &AccountInfo, + vault_base_token: &AccountInfo, + vault_quote_token: &AccountInfo, + maker_trader: &AccountInfo, +) { + // -- assume both maker and taker traders have seats + state::cvt_assume_main_trader_has_seat(trader.key); + cvt_assume_second_trader_has_seat(maker_trader.key); + + // -- assume market has proper base and quote vaults + let market_base_vault_pk: Pubkey = get_base_vault!(market); + let market_quote_vault_pk: Pubkey = get_quote_vault!(market); + cvt_assume!(vault_base_token.key == &market_base_vault_pk); + cvt_assume!(vault_quote_token.key == &market_quote_vault_pk); + // -- assume base and quote vaults are different + cvt_assume!(market_base_vault_pk != market_quote_vault_pk); + + // -- maker and taker traders are distinct + cvt_assume!(trader.key != maker_trader.key); +} + +/// Basic market pre-conditions +pub fn cvt_assume_market_preconditions( + market: &AccountInfo, + trader: &AccountInfo, + vault_base_token: &AccountInfo, + vault_quote_token: &AccountInfo, + maker_trader: &AccountInfo, +) -> DataIndex { + // -- assume both maker and taker traders have seats + crate::state::cvt_assume_main_trader_has_seat(trader.key); + crate::state::cvt_assume_second_trader_has_seat(maker_trader.key); + + // -- assume market has proper base and quote vaults + let market_base_vault_pk: Pubkey = get_base_vault!(market); + let market_quote_vault_pk: Pubkey = get_quote_vault!(market); + cvt_assume!(vault_base_token.key == &market_base_vault_pk); + cvt_assume!(vault_quote_token.key == &market_quote_vault_pk); + // -- assume base and quote vaults are different + cvt_assume!(market_base_vault_pk != market_quote_vault_pk); + + // -- maker and taker traders are distinct + cvt_assume!(trader.key != maker_trader.key); + + let maker_trader_index: DataIndex = second_trader_index(); + + // we assume that the slot into which our new order could rest is free, + // while the slot in the other book that we want to try and match with is filled + if IS_BID { + cvt_assume!(is_bid_order_free()); + cvt_assume!(is_ask_order_taken()); + } else { + cvt_assume!(is_ask_order_free()); + cvt_assume!(is_bid_order_taken()); + } + + // -- get index of the maker order, based on the book we expect it to be in + let maker_order_index: DataIndex = if IS_BID { + main_ask_order_index() + } else { + main_bid_order_index() + }; + + // -- assume maker order is sane and not global + let dynamic: &mut [u8; 8] = &mut [0; 8]; + let maker_order: &RestingOrder = get_helper_order(dynamic, maker_order_index).get_value(); + cvt_assume!(maker_order.get_is_bid() == !IS_BID); + cvt_assume!(maker_order.get_order_type() != OrderType::Global); + cvt_assume!(maker_order.get_trader_index() == maker_trader_index); + cvt_assume!(maker_order.get_num_base_atoms() == BaseAtoms::new(nondet())); + cvt_assume!(maker_order.get_price() == QuoteAtomsPerBaseAtom::nondet_price_u32()); + + maker_order_index +} + +pub fn cvt_assume_funds_invariants(balances: AllBalances) { + let AllBalances { + vault_base, + vault_quote, + withdrawable_base, + orderbook_base, + withdrawable_quote, + orderbook_quote, + trader_base, + trader_quote, + maker_trader_base, + maker_trader_quote, + maker_order_base, + maker_order_quote, + } = balances; + + // -- the sum of the trader amounts is less than aggregates + cvt_assume!(trader_base.checked_add(maker_trader_base).unwrap() <= withdrawable_base); + cvt_assume!(trader_quote.checked_add(maker_trader_quote).unwrap() <= withdrawable_quote); + + // -- maker order amounts are less than aggregates + cvt_assume!(maker_order_base <= orderbook_base); + cvt_assume!(maker_order_quote <= orderbook_quote); + + // -- vaults have enough funds to cover all obligations + cvt_assume!(vault_base == withdrawable_base.checked_add(orderbook_base).unwrap()); + cvt_assume!(vault_quote == withdrawable_quote.checked_add(orderbook_quote).unwrap()); +} + +pub fn cvt_assert_funds_invariants(balances: AllBalances) { + let AllBalances { + vault_base, + vault_quote, + withdrawable_base, + orderbook_base, + withdrawable_quote, + orderbook_quote, + trader_base, + trader_quote, + maker_trader_base, + maker_trader_quote, + maker_order_base: _, + maker_order_quote: _, + } = balances; + + // using non-checked arithmetic in the assertion to not hide any potentially bad executions + + // -- the sum of the trader amounts is less than aggregates + cvt_assert!(trader_base.saturating_add(maker_trader_base) <= withdrawable_base); + cvt_assert!(trader_quote.saturating_add(maker_trader_quote) <= withdrawable_quote); + + // -- vaults have enough funds to cover all obligations + cvt_assert!(vault_base == withdrawable_base.saturating_add(orderbook_base)); + cvt_assert!(vault_quote == withdrawable_quote.saturating_add(orderbook_quote)); +} + +pub fn cvt_assert_place_single_order_canceled_extra( + balances_old: AllBalances, + balances_new: AllBalances, +) { + let AllBalances { + vault_base: vault_base_old, + vault_quote: vault_quote_old, + withdrawable_base: withdrawable_base_old, + orderbook_base: orderbook_base_old, + withdrawable_quote: withdrawable_quote_old, + orderbook_quote: orderbook_quote_old, + trader_base: _trader_base_old, + trader_quote: _trader_quote_old, + maker_trader_base: _maker_trader_base_old, + maker_trader_quote: _maker_trader_quote_old, + maker_order_base: _maker_order_base_old, + maker_order_quote: _maker_order_quote_old, + } = balances_old; + + let AllBalances { + vault_base: vault_base_new, + vault_quote: vault_quote_new, + withdrawable_base: withdrawable_base_new, + orderbook_base: orderbook_base_new, + withdrawable_quote: withdrawable_quote_new, + orderbook_quote: orderbook_quote_new, + trader_base: _trader_base_new, + trader_quote: _trader_quote_new, + maker_trader_base: _maker_trader_base_new, + maker_trader_quote: _maker_trader_quote_new, + maker_order_base: _maker_order_base_new, + maker_order_quote: _maker_order_quote_new, + } = balances_new; + + // -- additional asserts + cvt_assert!(vault_base_old == vault_base_new); + cvt_assert!(vault_quote_old == vault_quote_new); + cvt_assert!( + withdrawable_base_old.saturating_add(orderbook_base_old) + == withdrawable_base_new.saturating_add(orderbook_base_new) + ); + cvt_assert!( + withdrawable_quote_old.saturating_add(orderbook_quote_old) + == withdrawable_quote_new.saturating_add(orderbook_quote_new) + ); +} + +pub fn cvt_assert_place_single_order_unmatched_extra( + balances_old: AllBalances, + balances_new: AllBalances, +) { + let AllBalances { + vault_base: vault_base_old, + vault_quote: vault_quote_old, + withdrawable_base: withdrawable_base_old, + orderbook_base: orderbook_base_old, + withdrawable_quote: withdrawable_quote_old, + orderbook_quote: orderbook_quote_old, + trader_base: _trader_base_old, + trader_quote: _trader_quote_old, + maker_trader_base: _maker_trader_base_old, + maker_trader_quote: _maker_trader_quote_old, + maker_order_base: _maker_order_base_old, + maker_order_quote: _maker_order_quote_old, + } = balances_old; + + let AllBalances { + vault_base: vault_base_new, + vault_quote: vault_quote_new, + withdrawable_base: withdrawable_base_new, + orderbook_base: orderbook_base_new, + withdrawable_quote: withdrawable_quote_new, + orderbook_quote: orderbook_quote_new, + trader_base: _trader_base_new, + trader_quote: _trader_quote_new, + maker_trader_base: _maker_trader_base_new, + maker_trader_quote: _maker_trader_quote_new, + maker_order_base: _maker_order_base_new, + maker_order_quote: _maker_order_quote_new, + } = balances_new; + + // -- additional asserts + cvt_assert!(withdrawable_base_new == withdrawable_base_old); + cvt_assert!(withdrawable_quote_new == withdrawable_quote_old); + cvt_assert!(orderbook_base_new == orderbook_base_old); + cvt_assert!(orderbook_quote_new == orderbook_quote_old); + cvt_assert!(vault_base_old == vault_base_new); + cvt_assert!(vault_quote_old == vault_quote_new); + cvt_assert!( + withdrawable_base_old.saturating_add(orderbook_base_old) + == withdrawable_base_new.saturating_add(orderbook_base_new) + ); + cvt_assert!( + withdrawable_quote_old.saturating_add(orderbook_quote_old) + == withdrawable_quote_new.saturating_add(orderbook_quote_new) + ); +} + +pub fn cvt_assert_place_single_order_full_match_extra( + balances_old: AllBalances, + balances_new: AllBalances, + total_base_atoms_traded: BaseAtoms, + total_quote_atoms_traded: QuoteAtoms, +) { + let AllBalances { + vault_base: vault_base_old, + vault_quote: vault_quote_old, + withdrawable_base: withdrawable_base_old, + orderbook_base: orderbook_base_old, + withdrawable_quote: withdrawable_quote_old, + orderbook_quote: orderbook_quote_old, + trader_base: _trader_base_old, + trader_quote: _trader_quote_old, + maker_trader_base: _maker_trader_base_old, + maker_trader_quote: _maker_trader_quote_old, + maker_order_base: _maker_order_base_old, + maker_order_quote: _maker_order_quote_old, + } = balances_old; + + let AllBalances { + vault_base: vault_base_new, + vault_quote: vault_quote_new, + withdrawable_base: withdrawable_base_new, + orderbook_base: orderbook_base_new, + withdrawable_quote: withdrawable_quote_new, + orderbook_quote: orderbook_quote_new, + trader_base: _trader_base_new, + trader_quote: _trader_quote_new, + maker_trader_base: _maker_trader_base_new, + maker_trader_quote: _maker_trader_quote_new, + maker_order_base: _maker_order_base_new, + maker_order_quote: _maker_order_quote_new, + } = balances_new; + + if IS_BID { + cvt_assert!(total_base_atoms_traded.as_u64() <= orderbook_base_old); + cvt_assert!(orderbook_base_new <= orderbook_base_old); + cvt_assert!( + orderbook_base_old.saturating_sub(orderbook_base_new) + == total_base_atoms_traded.as_u64() + ); + cvt_assert!(withdrawable_base_new >= withdrawable_base_old); + cvt_assert!( + withdrawable_base_new.saturating_sub(withdrawable_base_old) + == orderbook_base_old.saturating_sub(orderbook_base_new) + ); + cvt_assert!(withdrawable_quote_old == withdrawable_quote_new); + cvt_assert!(orderbook_quote_old == orderbook_quote_new); + } else { + cvt_assert!(total_quote_atoms_traded.as_u64() <= orderbook_quote_old); + cvt_assert!(orderbook_quote_new <= orderbook_quote_old); + cvt_assert!( + orderbook_quote_old.saturating_sub(orderbook_quote_new) + <= total_quote_atoms_traded.as_u64().saturating_add(1) + ); + cvt_assert!( + orderbook_quote_old.saturating_sub(orderbook_quote_new) + >= total_quote_atoms_traded.as_u64() + ); + cvt_assert!(withdrawable_quote_new >= withdrawable_quote_old); + cvt_assert!( + withdrawable_quote_new.saturating_sub(withdrawable_quote_old) + == orderbook_quote_old.saturating_sub(orderbook_quote_new) + ); + cvt_assert!(withdrawable_base_old == withdrawable_base_new); + cvt_assert!(orderbook_base_old == orderbook_base_new); + } + cvt_assert!(vault_base_old == vault_base_new); + cvt_assert!(vault_quote_old == vault_quote_new); + cvt_assert!( + withdrawable_base_old.saturating_add(orderbook_base_old) + == withdrawable_base_new.saturating_add(orderbook_base_new) + ); + cvt_assert!( + withdrawable_quote_old.saturating_add(orderbook_quote_old) + == withdrawable_quote_new.saturating_add(orderbook_quote_new) + ); +} + +pub fn cvt_assert_place_single_order_partial_match_extra( + balances_old: AllBalances, + balances_new: AllBalances, + total_base_atoms_traded: BaseAtoms, + total_quote_atoms_traded: QuoteAtoms, +) { + let AllBalances { + vault_base: vault_base_old, + vault_quote: vault_quote_old, + withdrawable_base: withdrawable_base_old, + orderbook_base: orderbook_base_old, + withdrawable_quote: withdrawable_quote_old, + orderbook_quote: orderbook_quote_old, + trader_base: _trader_base_old, + trader_quote: _trader_quote_old, + maker_trader_base: _maker_trader_base_old, + maker_trader_quote: _maker_trader_quote_old, + maker_order_base: _maker_order_base_old, + maker_order_quote: _maker_order_quote_old, + } = balances_old; + + let AllBalances { + vault_base: vault_base_new, + vault_quote: vault_quote_new, + withdrawable_base: withdrawable_base_new, + orderbook_base: orderbook_base_new, + withdrawable_quote: withdrawable_quote_new, + orderbook_quote: orderbook_quote_new, + trader_base: _trader_base_new, + trader_quote: _trader_quote_new, + maker_trader_base: _maker_trader_base_new, + maker_trader_quote: _maker_trader_quote_new, + maker_order_base: _maker_order_base_new, + maker_order_quote: _maker_order_quote_new, + } = balances_new; + + if IS_BID { + // -- additional assertions + cvt_assert!(total_base_atoms_traded.as_u64() <= orderbook_base_old); + cvt_assert!(orderbook_base_new <= orderbook_base_old); + cvt_assert!( + orderbook_base_old.saturating_sub(orderbook_base_new) + == total_base_atoms_traded.as_u64() + ); + cvt_assert!(withdrawable_base_new >= withdrawable_base_old); + cvt_assert!( + withdrawable_base_new.saturating_sub(withdrawable_base_old) + == orderbook_base_old.saturating_sub(orderbook_base_new) + ); + cvt_assert!(withdrawable_quote_old == withdrawable_quote_new); + cvt_assert!(orderbook_quote_old == orderbook_quote_new); + } else { + // -- additional assertions + cvt_assert!(total_quote_atoms_traded.as_u64() <= orderbook_quote_old); + cvt_assert!(orderbook_quote_new <= orderbook_quote_old); + cvt_assert!( + orderbook_quote_old.saturating_sub(orderbook_quote_new) + <= total_quote_atoms_traded.as_u64().saturating_add(1) + ); + cvt_assert!( + orderbook_quote_old.saturating_sub(orderbook_quote_new) + >= total_quote_atoms_traded.as_u64() + ); + cvt_assert!(withdrawable_quote_new >= withdrawable_quote_old); + cvt_assert!( + withdrawable_quote_new.saturating_sub(withdrawable_quote_old) + == orderbook_quote_old.saturating_sub(orderbook_quote_new) + ); + cvt_assert!(withdrawable_base_old == withdrawable_base_new); + cvt_assert!(orderbook_base_old == orderbook_base_new); + } + cvt_assert!(vault_base_old == vault_base_new); + cvt_assert!(vault_quote_old == vault_quote_new); + cvt_assert!( + withdrawable_base_old.saturating_add(orderbook_base_old) + == withdrawable_base_new.saturating_add(orderbook_base_new) + ); + cvt_assert!( + withdrawable_quote_old.saturating_add(orderbook_quote_old) + == withdrawable_quote_new.saturating_add(orderbook_quote_new) + ); +} + +pub fn cvt_assert_deposit_extra( + balances_old: AllBalances, + balances_new: AllBalances, + amount: u64, +) { + let AllBalances { + vault_base: vault_base_old, + vault_quote: vault_quote_old, + withdrawable_base: withdrawable_base_old, + orderbook_base: orderbook_base_old, + withdrawable_quote: withdrawable_quote_old, + orderbook_quote: orderbook_quote_old, + trader_base: trader_base_old, + trader_quote: trader_quote_old, + maker_trader_base: _maker_trader_base_old, + maker_trader_quote: _maker_trader_quote_old, + maker_order_base: _maker_order_base_old, + maker_order_quote: _maker_order_quote_old, + } = balances_old; + + let AllBalances { + vault_base: vault_base_new, + vault_quote: vault_quote_new, + withdrawable_base: withdrawable_base_new, + orderbook_base: orderbook_base_new, + withdrawable_quote: withdrawable_quote_new, + orderbook_quote: orderbook_quote_new, + trader_base: trader_base_new, + trader_quote: trader_quote_new, + maker_trader_base: _maker_trader_base_new, + maker_trader_quote: _maker_trader_quote_new, + maker_order_base: _maker_order_base_new, + maker_order_quote: _maker_order_quote_new, + } = balances_new; + + cvt_assert!(orderbook_base_old == orderbook_base_new); + cvt_assert!(orderbook_quote_old == orderbook_quote_new); + if IS_BASE { + cvt_assert!(trader_quote_new == trader_quote_old); + cvt_assert!(withdrawable_quote_new == withdrawable_quote_old); + cvt_assert!(vault_quote_new == vault_quote_old); + cvt_assert!(trader_base_old.saturating_add(amount) == trader_base_new); + cvt_assert!(vault_base_old.saturating_add(amount) == vault_base_new); + } else { + cvt_assert!(trader_base_new == trader_base_old); + cvt_assert!(withdrawable_base_new == withdrawable_base_old); + cvt_assert!(vault_base_new == vault_base_old); + cvt_assert!(trader_quote_old.saturating_add(amount) == trader_quote_new); + cvt_assert!(vault_quote_old.saturating_add(amount) == vault_quote_new); + } +} + +pub fn cvt_assert_withdraw_extra( + balances_old: AllBalances, + balances_new: AllBalances, + amount: u64, +) { + let AllBalances { + vault_base: vault_base_old, + vault_quote: vault_quote_old, + withdrawable_base: withdrawable_base_old, + orderbook_base: orderbook_base_old, + withdrawable_quote: withdrawable_quote_old, + orderbook_quote: orderbook_quote_old, + trader_base: trader_base_old, + trader_quote: trader_quote_old, + maker_trader_base: _maker_trader_base_old, + maker_trader_quote: _maker_trader_quote_old, + maker_order_base: _maker_order_base_old, + maker_order_quote: _maker_order_quote_old, + } = balances_old; + + let AllBalances { + vault_base: vault_base_new, + vault_quote: vault_quote_new, + withdrawable_base: withdrawable_base_new, + orderbook_base: orderbook_base_new, + withdrawable_quote: withdrawable_quote_new, + orderbook_quote: orderbook_quote_new, + trader_base: trader_base_new, + trader_quote: trader_quote_new, + maker_trader_base: _maker_trader_base_new, + maker_trader_quote: _maker_trader_quote_new, + maker_order_base: _maker_order_base_new, + maker_order_quote: _maker_order_quote_new, + } = balances_new; + + cvt_assert!(orderbook_base_old == orderbook_base_new); + cvt_assert!(orderbook_quote_old == orderbook_quote_new); + if IS_BASE { + cvt_assert!(trader_quote_new == trader_quote_old); + cvt_assert!(withdrawable_quote_new == withdrawable_quote_old); + cvt_assert!(vault_quote_new == vault_quote_old); + cvt_assert!(trader_base_old.saturating_sub(amount) == trader_base_new); + cvt_assert!(vault_base_old.saturating_sub(amount) == vault_base_new); + } else { + cvt_assert!(trader_base_new == trader_base_old); + cvt_assert!(withdrawable_base_new == withdrawable_base_old); + cvt_assert!(vault_base_new == vault_base_old); + cvt_assert!(trader_quote_old.saturating_sub(amount) == trader_quote_new); + cvt_assert!(vault_quote_old.saturating_sub(amount) == vault_quote_new); + } +} diff --git a/programs/manifest/src/certora/spec/place_order_checks.rs b/programs/manifest/src/certora/spec/place_order_checks.rs new file mode 100644 index 000000000..53cd7d3b6 --- /dev/null +++ b/programs/manifest/src/certora/spec/place_order_checks.rs @@ -0,0 +1,344 @@ +use crate::*; +use cvt::{cvt_assume, cvt_vacuity_check}; +use cvt_macros::rule; +use nondet::*; + +use solana_program::account_info::AccountInfo; + +use state::main_trader_index; + +use crate::{ + certora::spec::no_funds_loss_util::*, + program::get_mut_dynamic_account, + quantities::{BaseAtoms, QuoteAtomsPerBaseAtom}, + state::{ + market::market_helpers::{AddOrderStatus, AddOrderToMarketInnerResult, AddSingleOrderCtx}, + AddOrderToMarketArgs, DynamicAccount, MarketRefMut, + }, +}; +use hypertree::DataIndex; + +pub fn place_single_order_nondet_inputs( + market_info: &AccountInfo, +) -> (AddOrderToMarketArgs<'static, 'static>, BaseAtoms, u32) { + let args: AddOrderToMarketArgs = AddOrderToMarketArgs { + market: *market_info.key, + trader_index: main_trader_index(), + num_base_atoms: nondet(), + price: QuoteAtomsPerBaseAtom::nondet_price_u32(), + is_bid: IS_BID, + last_valid_slot: nondet(), + order_type: state::OrderType::Limit, + global_trade_accounts_opts: &[None, None], + current_slot: Some(nondet()), + }; + let remaining_base_atoms: BaseAtoms = nondet(); + let now_slot: u32 = nondet(); + (args, remaining_base_atoms, now_slot) +} + +pub fn place_single_order_canceled_check() { + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let trader: &AccountInfo = &acc_infos[0]; + let market_info: &AccountInfo = &acc_infos[1]; + let maker_trader: &AccountInfo = &acc_infos[7]; + let vault_base_token: &AccountInfo = &acc_infos[8]; + let vault_quote_token: &AccountInfo = &acc_infos[9]; + + // -- market preconditions + let maker_order_index: DataIndex = cvt_assume_market_preconditions::( + market_info, + trader, + vault_base_token, + vault_quote_token, + maker_trader, + ); + + let balances_old: AllBalances = record_all_balances( + market_info, + vault_base_token, + vault_quote_token, + trader, + maker_trader, + maker_order_index, + ); + + // -- assume no loss of funds invariant + cvt_assume_funds_invariants(balances_old); + + let (args, remaining_base_atoms, now_slot) = + place_single_order_nondet_inputs::(market_info); + + // -- call to place_single_order + let (res, _total_base_atoms_traded, _total_quote_atoms_traded) = place_single_order!( + market_info, + args, + remaining_base_atoms, + now_slot, + maker_order_index + ); + cvt_assume!(res.status == AddOrderStatus::Canceled); + + let balances_new: AllBalances = record_all_balances( + market_info, + vault_base_token, + vault_quote_token, + trader, + maker_trader, + maker_order_index, + ); + + // -- assert no loss of funds invariant + cvt_assert_funds_invariants(balances_new); + + // -- additional assertions + cvt_assert_place_single_order_canceled_extra::(balances_old, balances_new); + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_place_single_order_canceled_bid() { + place_single_order_canceled_check::(); +} + +#[rule] +pub fn rule_place_single_order_canceled_ask() { + place_single_order_canceled_check::(); +} + +pub fn place_single_order_unmatched_check() { + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let trader: &AccountInfo = &acc_infos[0]; + let market_info: &AccountInfo = &acc_infos[1]; + + let maker_trader: &AccountInfo = &acc_infos[7]; + let vault_base_token: &AccountInfo = &acc_infos[8]; + let vault_quote_token: &AccountInfo = &acc_infos[9]; + + let maker_order_index: DataIndex = cvt_assume_market_preconditions::( + market_info, + trader, + vault_base_token, + vault_quote_token, + maker_trader, + ); + + // -- record balances + + let balances_old: AllBalances = record_all_balances( + market_info, + vault_base_token, + vault_quote_token, + trader, + maker_trader, + maker_order_index, + ); + + // -- assume no loss of funds invariant + cvt_assume_funds_invariants(balances_old); + + let (args, remaining_base_atoms, now_slot) = + place_single_order_nondet_inputs::(market_info); + + // -- call to place_single_order + let (res, _total_base_atoms_traded, _total_quote_atoms_traded) = place_single_order!( + market_info, + args, + remaining_base_atoms, + now_slot, + maker_order_index + ); + cvt_assume!(res.status == AddOrderStatus::Unmatched); + + let balances_new: AllBalances = record_all_balances( + market_info, + vault_base_token, + vault_quote_token, + trader, + maker_trader, + maker_order_index, + ); + + // -- assert no loss of funds invariant + cvt_assert_funds_invariants(balances_new); + + // -- additional assertions + cvt_assert_place_single_order_canceled_extra::(balances_old, balances_new); + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_place_single_order_unmatched_bid() { + place_single_order_unmatched_check::(); +} + +#[rule] +pub fn rule_place_single_order_unmatched_ask() { + place_single_order_unmatched_check::(); +} + +pub fn place_single_order_full_match_check() { + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let trader: &AccountInfo = &acc_infos[0]; + let market_info: &AccountInfo = &acc_infos[1]; + + let maker_trader: &AccountInfo = &acc_infos[7]; + let vault_base_token: &AccountInfo = &acc_infos[8]; + let vault_quote_token: &AccountInfo = &acc_infos[9]; + + // -- market assumptions + + let maker_order_index: DataIndex = cvt_assume_market_preconditions::( + market_info, + trader, + vault_base_token, + vault_quote_token, + maker_trader, + ); + + // -- record balances before place_single_order + + let balances_old: AllBalances = record_all_balances( + market_info, + vault_base_token, + vault_quote_token, + trader, + maker_trader, + maker_order_index, + ); + + // -- assume no loss of funds invariant + cvt_assume_funds_invariants(balances_old); + + let (args, remaining_base_atoms, now_slot) = + place_single_order_nondet_inputs::(market_info); + + // -- call to place_single_order + let (res, total_base_atoms_traded, total_quote_atoms_traded) = place_single_order!( + market_info, + args, + remaining_base_atoms, + now_slot, + maker_order_index + ); + cvt_assume!(res.status == AddOrderStatus::Filled); + + let balances_new: AllBalances = record_all_balances( + market_info, + vault_base_token, + vault_quote_token, + trader, + maker_trader, + maker_order_index, + ); + + // -- assert no loss of funds invariant + cvt_assert_funds_invariants(balances_new); + + // -- additional asserts + cvt_assert_place_single_order_full_match_extra::( + balances_old, + balances_new, + total_base_atoms_traded, + total_quote_atoms_traded, + ); + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_place_single_order_full_match_bid() { + place_single_order_full_match_check::(); +} + +#[rule] +pub fn rule_place_single_order_full_match_ask() { + place_single_order_full_match_check::(); +} + +pub fn place_single_order_partial_match_check() { + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let trader: &AccountInfo = &acc_infos[0]; + let market_info: &AccountInfo = &acc_infos[1]; + + let maker_trader: &AccountInfo = &acc_infos[7]; + let vault_base_token: &AccountInfo = &acc_infos[8]; + let vault_quote_token: &AccountInfo = &acc_infos[9]; + + // -- market preconditions + let maker_order_index: DataIndex = cvt_assume_market_preconditions::( + market_info, + trader, + vault_base_token, + vault_quote_token, + maker_trader, + ); + + // -- record balances before place_single_order + let balances_old: AllBalances = record_all_balances( + market_info, + vault_base_token, + vault_quote_token, + trader, + maker_trader, + maker_order_index, + ); + + // -- assume no loss of funds invariant + cvt_assume_funds_invariants(balances_old); + + let (args, remaining_base_atoms, now_slot) = + place_single_order_nondet_inputs::(market_info); + + // -- call to place_single_order + let (res, total_base_atoms_traded, total_quote_atoms_traded) = place_single_order!( + market_info, + args, + remaining_base_atoms, + now_slot, + maker_order_index + ); + cvt_assume!(res.status == AddOrderStatus::PartialFill); + + let balances_new: AllBalances = record_all_balances( + market_info, + vault_base_token, + vault_quote_token, + trader, + maker_trader, + maker_order_index, + ); + + // -- assert no loss of funds invariant + cvt_assert_funds_invariants(balances_new); + + // -- additional asserts + cvt_assert_place_single_order_partial_match_extra::( + balances_old, + balances_new, + total_base_atoms_traded, + total_quote_atoms_traded, + ); + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_place_single_order_partial_match_bid() { + place_single_order_partial_match_check::(); +} + +#[rule] +pub fn rule_place_single_order_partial_match_ask() { + place_single_order_partial_match_check::(); +} diff --git a/programs/manifest/src/certora/spec/rbtree_checks.rs b/programs/manifest/src/certora/spec/rbtree_checks.rs new file mode 100644 index 000000000..229e6dd74 --- /dev/null +++ b/programs/manifest/src/certora/spec/rbtree_checks.rs @@ -0,0 +1,4724 @@ +use super::verification_utils::init_static; +use bytemuck::{Pod, Zeroable}; +use cvt::{cvt_assert, cvt_assume, cvt_vacuity_check}; +use cvt_macros::rule; +pub use hypertree::red_black_tree::*; +use hypertree::{ + get_helper, get_mut_helper, DataIndex, HyperTreeReadOperations, HyperTreeWriteOperations, NIL, +}; +use nondet::*; +use solana_program::account_info::AccountInfo; +use std::{cmp::Ordering, fmt::Display}; + +#[derive(Copy, Clone, Pod, Zeroable, Debug)] +#[repr(C)] +pub struct TestOrder { + order_id: u64, +} + +impl Ord for TestOrder { + fn cmp(&self, other: &Self) -> Ordering { + (self.order_id).cmp(&(other.order_id)) + } +} + +impl PartialOrd for TestOrder { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for TestOrder { + fn eq(&self, other: &Self) -> bool { + (self.order_id) == (other.order_id) + } +} + +impl Eq for TestOrder {} + +impl Display for TestOrder { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.order_id) + } +} + +impl TestOrder { + pub fn new(order_id: u64) -> Self { + TestOrder { order_id } + } +} + +// Blocks are +// Left: DataIndex +// Right: DataIndex +// Parent: DataIndex +// Color: DataIndex +// TestOrder: 8 +// 8 + 8 + 8 + 8 + 8 = 40 +const TEST_BLOCK_WIDTH: DataIndex = 40; + +macro_rules! mk_rb_node { + ($left: expr, $right: expr, $parent: expr, $color: expr, $value: expr) => {{ + let left_nd = nondet::(); + let right_nd = nondet::(); + let parent_nd = nondet::(); + let color_nd = if (nondet::()) { + Color::Black + } else { + Color::Red + }; + cvt_assume!(left_nd == $left); + cvt_assume!(right_nd == $right); + cvt_assume!(parent_nd == $parent); + cvt_assume!(color_nd == $color); + RBNode { + left: left_nd, + right: right_nd, + parent: parent_nd, + color: color_nd, + payload_type: 0, + _unused_padding: 0, + value: $value, + } + }}; +} + +// The rules for rotation (both left and right) hardcode the following facts: +// 1. position of nodes +// 2. color of the nodes (GG is black, G is red, etc.) +// 3. G is left child of GG +// 4. U, Y and X have no children + +// The rules currently do not check that colors and values of the nodes are unmodified by rotations + +// Left rotate of G +// +// GG GG +// | | +// G P +// / \ / \ +// U P ---> G X +// / \ / \ +// Y X U Y +#[rule] +pub fn rule_rotate_left() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let gg_index = 0 * TEST_BLOCK_WIDTH; + let g_index = 1 * TEST_BLOCK_WIDTH; + let u_index = 2 * TEST_BLOCK_WIDTH; + let p_index = 3 * TEST_BLOCK_WIDTH; + let y_index = 4 * TEST_BLOCK_WIDTH; + let x_index = 5 * TEST_BLOCK_WIDTH; + + let gg_val: u64 = nondet(); + let g_val: u64 = nondet(); + let u_val: u64 = nondet(); + let p_val: u64 = nondet(); + let y_val: u64 = nondet(); + let x_val: u64 = nondet(); + + // GG + *get_mut_helper(&mut data, gg_index) = + mk_rb_node!(g_index, NIL, NIL, Color::Black, TestOrder::new(gg_val)); + + // G + *get_mut_helper(&mut data, g_index) = mk_rb_node!( + u_index, + p_index, + gg_index, + Color::Red, + TestOrder::new(g_val) + ); + + // U + *get_mut_helper(&mut data, u_index) = + mk_rb_node!(NIL, NIL, g_index, Color::Black, TestOrder::new(u_val)); + + // P + *get_mut_helper(&mut data, p_index) = mk_rb_node!( + y_index, + x_index, + g_index, + Color::Black, + TestOrder::new(p_val) + ); + + // Y + *get_mut_helper(&mut data, y_index) = + mk_rb_node!(NIL, NIL, p_index, Color::Red, TestOrder::new(y_val)); + + // X + *get_mut_helper(&mut data, x_index) = + mk_rb_node!(NIL, NIL, p_index, Color::Red, TestOrder::new(x_val)); + let mut tree: RedBlackTree = RedBlackTree::new(&mut data, gg_index, NIL); + + tree.rotate_left::(g_index); + + // Asserts + + // GG asserts + let gg_left = tree.get_left_index::(gg_index); + cvt_assert!(gg_left == p_index); + + // P asserts + let p_parent = tree.get_parent_index::(p_index); + let p_left = tree.get_left_index::(p_index); + let p_right = tree.get_right_index::(p_index); + cvt_assert!(p_parent == gg_index); + cvt_assert!(p_left == g_index); + cvt_assert!(p_right == x_index); + + // G asserts + let g_parent = tree.get_parent_index::(g_index); + let g_left = tree.get_left_index::(g_index); + let g_right = tree.get_right_index::(g_index); + cvt_assert!(g_parent == p_index); + cvt_assert!(g_left == u_index); + cvt_assert!(g_right == y_index); + + // X asserts + let x_parent = tree.get_parent_index::(x_index); + cvt_assert!(x_parent == p_index); + + // U asserts + let u_parent = tree.get_parent_index::(u_index); + cvt_assert!(u_parent == g_index); + + // Y asserts + let y_parent = tree.get_parent_index::(y_index); + cvt_assert!(y_parent == g_index); + + cvt_vacuity_check!(); +} + +// Right rotate of G +// +// GG GG +// | | +// G P +// / \ / \ +// P U ---> X G +// / \ / \ +// X Y Y U +#[rule] +pub fn rule_rotate_right() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let gg_index = 0 * TEST_BLOCK_WIDTH; + let g_index = 1 * TEST_BLOCK_WIDTH; + let u_index = 2 * TEST_BLOCK_WIDTH; + let p_index = 3 * TEST_BLOCK_WIDTH; + let y_index = 4 * TEST_BLOCK_WIDTH; + let x_index = 5 * TEST_BLOCK_WIDTH; + + let gg_val: u64 = nondet(); + let g_val: u64 = nondet(); + let u_val: u64 = nondet(); + let p_val: u64 = nondet(); + let y_val: u64 = nondet(); + let x_val: u64 = nondet(); + + // GG + *get_mut_helper(&mut data, gg_index) = + mk_rb_node!(g_index, NIL, NIL, Color::Black, TestOrder::new(gg_val)); + + // G + *get_mut_helper(&mut data, g_index) = mk_rb_node!( + p_index, + u_index, + gg_index, + Color::Red, + TestOrder::new(g_val) + ); + + // P + *get_mut_helper(&mut data, p_index) = mk_rb_node!( + x_index, + y_index, + g_index, + Color::Black, + TestOrder::new(p_val) + ); + + // U + *get_mut_helper(&mut data, u_index) = + mk_rb_node!(NIL, NIL, g_index, Color::Black, TestOrder::new(u_val)); + + // X + *get_mut_helper(&mut data, x_index) = + mk_rb_node!(NIL, NIL, p_index, Color::Red, TestOrder::new(x_val)); + + // Y + *get_mut_helper(&mut data, y_index) = + mk_rb_node!(NIL, NIL, p_index, Color::Red, TestOrder::new(y_val)); + + let mut tree: RedBlackTree = RedBlackTree::new(&mut data, gg_index, NIL); + + tree.rotate_right::(g_index); + + // Asserts + + // GG asserts + let gg_left = tree.get_left_index::(gg_index); + cvt_assert!(gg_left == p_index); + + // P asserts + let p_parent = tree.get_parent_index::(p_index); + let p_left = tree.get_left_index::(p_index); + let p_right = tree.get_right_index::(p_index); + cvt_assert!(p_parent == gg_index); + cvt_assert!(p_left == x_index); + cvt_assert!(p_right == g_index); + + // G asserts + let g_parent = tree.get_parent_index::(g_index); + let g_left = tree.get_left_index::(g_index); + let g_right = tree.get_right_index::(g_index); + cvt_assert!(g_parent == p_index); + cvt_assert!(g_left == y_index); + cvt_assert!(g_right == u_index); + + // X asserts + let x_parent = tree.get_parent_index::(x_index); + cvt_assert!(x_parent == p_index); + + // U asserts + let u_parent = tree.get_parent_index::(u_index); + cvt_assert!(u_parent == g_index); + + // Y asserts + let y_parent = tree.get_parent_index::(y_index); + cvt_assert!(y_parent == g_index); + + cvt_vacuity_check!(); +} + +// The rules for checking that the parent of the left and right child of a node +// is the node itself hardcode the following facts: +// 1. position of nodes +// 2. color of the nodes +// 3. 0 is the root +// 4. 1 is the left child of 0, its value is lesser or equal than the value of 0 +// 5. 2 is the right child of 0, its value is greater or equal than the value of 0 +// 6. 3 is the left child of 1, its value is lesser or equal than the value of 1 +// 7. 4 is the right child of 1, its value is greater or equal than the value of 1 +// 8. 2, 3, and 4 have no children + +/// For each node, check that the parent of the left child is the node itself. +/// Check this before and after inserting a new node in the following tree. +/// Run with `-bmc 2`. +/// +/// 0 +/// / \ +/// 1 2 +/// / \ +/// 3 4 +/// +#[rule] +pub fn rule_insert_preserves_parent_of_left_child() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let zero_index = 0 * TEST_BLOCK_WIDTH; + let one_index = 1 * TEST_BLOCK_WIDTH; + let two_index = 2 * TEST_BLOCK_WIDTH; + let three_index = 3 * TEST_BLOCK_WIDTH; + let four_index = 4 * TEST_BLOCK_WIDTH; + let five_index = 5 * TEST_BLOCK_WIDTH; + + let zero_val: u64 = nondet(); + let one_val: u64 = nondet_with(|x| *x <= zero_val); + let two_val: u64 = nondet_with(|x| *x >= zero_val); + let three_val: u64 = nondet_with(|x| *x <= one_val); + let four_val: u64 = nondet_with(|x| *x >= one_val); + let five_val: u64 = nondet(); + + // 0 + *get_mut_helper(&mut data, zero_index) = mk_rb_node!( + one_index, + two_index, + NIL, + Color::Black, + TestOrder::new(zero_val) + ); + + // 1 + *get_mut_helper(&mut data, one_index) = mk_rb_node!( + three_index, + four_index, + zero_index, + Color::Red, + TestOrder::new(one_val) + ); + + // 2 + *get_mut_helper(&mut data, two_index) = + mk_rb_node!(NIL, NIL, zero_index, Color::Black, TestOrder::new(two_val)); + + // 3 + *get_mut_helper(&mut data, three_index) = + mk_rb_node!(NIL, NIL, one_index, Color::Black, TestOrder::new(three_val)); + + // 4 + *get_mut_helper(&mut data, four_index) = + mk_rb_node!(NIL, NIL, one_index, Color::Black, TestOrder::new(four_val)); + + let mut tree: RedBlackTree = RedBlackTree::new(&mut data, zero_index, two_index); + + // Check that the parent of the left child of any node is the node itself + // This loop is unrolled enough times with -bmc 2 because the Rust compiler + // partially unrolls it at compile time. + let size_before_insert = 5; + for node_number in 0..size_before_insert { + let node_index = node_number * TEST_BLOCK_WIDTH; + let left_child_index = tree.get_left_index::(node_index); + if left_child_index != NIL { + let left_child_parent_index = tree.get_parent_index::(left_child_index); + cvt_assert!(left_child_parent_index == node_index); + } + } + + // Insert node 5 + let five_order = TestOrder::new(five_val.into()); + tree.insert(five_index, five_order); + + // Check that the parent of the left child of any node is the node itself + // This loop is unrolled enough times with -bmc 2 because the Rust compiler + // partially unrolls it at compile time. + let size_after_insert = 6; + for node_number in 0..size_after_insert { + let node_index = node_number * TEST_BLOCK_WIDTH; + let left_child_index = tree.get_left_index::(node_index); + if left_child_index != NIL { + let left_child_parent_index = tree.get_parent_index::(left_child_index); + cvt_assert!(left_child_parent_index == node_index); + } + } + + cvt_vacuity_check!() +} + +/// For each node, check that the parent of the right child is the node itself. +/// Check this before and after inserting a new node in the following tree. +/// Run with `-bmc 2`. +/// +/// 0 +/// / \ +/// 1 2 +/// / \ +/// 3 4 +/// +#[rule] +pub fn rule_insert_preserves_parent_of_right_child() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let zero_index = 0 * TEST_BLOCK_WIDTH; + let one_index = 1 * TEST_BLOCK_WIDTH; + let two_index = 2 * TEST_BLOCK_WIDTH; + let three_index = 3 * TEST_BLOCK_WIDTH; + let four_index = 4 * TEST_BLOCK_WIDTH; + let five_index = 5 * TEST_BLOCK_WIDTH; + + let zero_val: u64 = nondet(); + let one_val: u64 = nondet_with(|x| *x <= zero_val); + let two_val: u64 = nondet_with(|x| *x >= zero_val); + let three_val: u64 = nondet_with(|x| *x <= one_val); + let four_val: u64 = nondet_with(|x| *x >= one_val); + let five_val: u64 = nondet(); + + // 0 + *get_mut_helper(&mut data, zero_index) = mk_rb_node!( + one_index, + two_index, + NIL, + Color::Black, + TestOrder::new(zero_val) + ); + + // 1 + *get_mut_helper(&mut data, one_index) = mk_rb_node!( + three_index, + four_index, + zero_index, + Color::Red, + TestOrder::new(one_val) + ); + + // 2 + *get_mut_helper(&mut data, two_index) = + mk_rb_node!(NIL, NIL, zero_index, Color::Black, TestOrder::new(two_val)); + + // 3 + *get_mut_helper(&mut data, three_index) = + mk_rb_node!(NIL, NIL, one_index, Color::Black, TestOrder::new(three_val)); + + // 4 + *get_mut_helper(&mut data, four_index) = + mk_rb_node!(NIL, NIL, one_index, Color::Black, TestOrder::new(four_val)); + + let mut tree: RedBlackTree = RedBlackTree::new(&mut data, zero_index, two_index); + + // Check that the parent of the right child of any node is the node itself + // This loop is unrolled enough times with -bmc 2 because the Rust compiler + // partially unrolls it at compile time. + let size_before_insert = 5; + for node_number in 0..size_before_insert { + let node_index = node_number * TEST_BLOCK_WIDTH; + let right_child_index = tree.get_right_index::(node_index); + if right_child_index != NIL { + let right_child_parent_index = tree.get_parent_index::(right_child_index); + cvt_assert!(right_child_parent_index == node_index); + } + } + + // Insert node 5 + let five_order = TestOrder::new(five_val.into()); + tree.insert(five_index, five_order); + + // Check that the parent of the right child of any node is the node itself + // This loop is unrolled enough times with -bmc 2 because the Rust compiler + // partially unrolls it at compile time. + let size_after_insert = 6; + for node_number in 0..size_after_insert { + let node_index = node_number * TEST_BLOCK_WIDTH; + let right_child_index = tree.get_right_index::(node_index); + if right_child_index != NIL { + let right_child_parent_index = tree.get_parent_index::(right_child_index); + cvt_assert!(right_child_parent_index == node_index); + } + } + + cvt_vacuity_check!() +} + +// The rules for checking that the parent of the root is NIL hardcode the following facts: +// 1. position of nodes +// 2. color of the nodes +// 3. 0 is the root +// 4. 1 is the left child of 0, its value is lesser or equal than the value of 0 +// 5. 2 is the right child of 0, its value is greater or equal than the value of 0 +// 6. 3 is the left child of 1, its value is lesser or equal than the value of 1 +// 7. 4 is the right child of 1, its value is greater or equal than the value of 1 +// 8. 2, 3, and 4 have no children + +/// Check that the parent of the root is NIL before and after inserting a node +/// in the following tree. The value of the new node is nondet. +/// Run with `-bmc 2`. +/// +/// 0 +/// / \ +/// 1 2 +/// / \ +/// 3 4 +/// +#[rule] +pub fn rule_insert_preserves_root_parent_is_nil() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let zero_index = 0 * TEST_BLOCK_WIDTH; + let one_index = 1 * TEST_BLOCK_WIDTH; + let two_index = 2 * TEST_BLOCK_WIDTH; + let three_index = 3 * TEST_BLOCK_WIDTH; + let four_index = 4 * TEST_BLOCK_WIDTH; + let five_index = 5 * TEST_BLOCK_WIDTH; + + let zero_val: u64 = nondet(); + let one_val: u64 = nondet_with(|x| *x <= zero_val); + let two_val: u64 = nondet_with(|x| *x >= zero_val); + let three_val: u64 = nondet_with(|x| *x <= one_val); + let four_val: u64 = nondet_with(|x| *x >= one_val); + let five_val: u64 = nondet(); + + // 0 + *get_mut_helper(&mut data, zero_index) = mk_rb_node!( + one_index, + two_index, + NIL, + Color::Black, + TestOrder::new(zero_val) + ); + + // 1 + *get_mut_helper(&mut data, one_index) = mk_rb_node!( + three_index, + four_index, + zero_index, + Color::Red, + TestOrder::new(one_val) + ); + + // 2 + *get_mut_helper(&mut data, two_index) = + mk_rb_node!(NIL, NIL, zero_index, Color::Black, TestOrder::new(two_val)); + + // 3 + *get_mut_helper(&mut data, three_index) = + mk_rb_node!(NIL, NIL, one_index, Color::Black, TestOrder::new(three_val)); + + // 4 + *get_mut_helper(&mut data, four_index) = + mk_rb_node!(NIL, NIL, one_index, Color::Black, TestOrder::new(four_val)); + + let mut tree: RedBlackTree = RedBlackTree::new(&mut data, zero_index, two_index); + + // Assert that the root parent is NIL before inserting node 5 + let root_parent = tree.get_parent_index::(tree.root_index()); + cvt_assert!(root_parent == NIL); + + // Insert node 5 + let five_order = TestOrder::new(five_val.into()); + tree.insert(five_index, five_order); + + // Assert that the root parent is NIL after inserting node 5 + let root_parent = tree.get_parent_index::(tree.root_index()); + cvt_assert!(root_parent == NIL); + + cvt_vacuity_check!(); +} + +/// Check that the root of a rb tree is black after inserting two nodes in an +/// empty tree. +#[rule] +pub fn rule_root_is_black_after_insert_empty_tree() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let mut tree: RedBlackTree = RedBlackTree::new(&mut data, NIL, NIL); + + // Insert the first node + tree.insert(0, TestOrder::new(nondet::())); + let root_index = tree.root_index(); + let root = get_helper::>(tree.data(), root_index); + // Check that the root is black + cvt_assert!(root.color == Color::Black); + + // Insert the second node + tree.insert(TEST_BLOCK_WIDTH, TestOrder::new(nondet::())); + let root_index = tree.root_index(); + let root = get_helper::>(tree.data(), root_index); + // Check that the root is black + cvt_assert!(root.color == Color::Black); + + cvt_vacuity_check!(); +} + +/// Check that the root of the following rb tree is black before and after a +/// node with nondet value. Run with `-bmc 2`. +/// +/// 0 +/// / \ +/// 1 2 +/// / \ +/// 3 4 +/// +#[rule] +pub fn rule_root_is_black_after_insert_non_empty_tree() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let zero_index = 0 * TEST_BLOCK_WIDTH; + let one_index = 1 * TEST_BLOCK_WIDTH; + let two_index = 2 * TEST_BLOCK_WIDTH; + let three_index = 3 * TEST_BLOCK_WIDTH; + let four_index = 4 * TEST_BLOCK_WIDTH; + let five_index = 5 * TEST_BLOCK_WIDTH; + + let zero_val: u64 = nondet(); + let one_val: u64 = nondet_with(|x| *x <= zero_val); + let two_val: u64 = nondet_with(|x| *x >= zero_val); + let three_val: u64 = nondet_with(|x| *x <= one_val); + let four_val: u64 = nondet_with(|x| *x >= one_val); + let five_val: u64 = nondet(); + + // 0 + *get_mut_helper(&mut data, zero_index) = mk_rb_node!( + one_index, + two_index, + NIL, + Color::Black, + TestOrder::new(zero_val) + ); + + // 1 + *get_mut_helper(&mut data, one_index) = mk_rb_node!( + three_index, + four_index, + zero_index, + Color::Red, + TestOrder::new(one_val) + ); + + // 2 + *get_mut_helper(&mut data, two_index) = + mk_rb_node!(NIL, NIL, zero_index, Color::Black, TestOrder::new(two_val)); + + // 3 + *get_mut_helper(&mut data, three_index) = + mk_rb_node!(NIL, NIL, one_index, Color::Black, TestOrder::new(three_val)); + + // 4 + *get_mut_helper(&mut data, four_index) = + mk_rb_node!(NIL, NIL, one_index, Color::Black, TestOrder::new(four_val)); + + let mut tree: RedBlackTree = RedBlackTree::new(&mut data, zero_index, two_index); + + let root_index = tree.root_index(); + let root = get_helper::>(tree.data(), root_index); + cvt_assert!(root.color == Color::Black); + + // Insert node 5 + let five_order = TestOrder::new(five_val.into()); + tree.insert(five_index, five_order); + + let root_index = tree.root_index(); + let root = get_helper::>(tree.data(), root_index); + cvt_assert!(root.color == Color::Black); + + cvt_vacuity_check!(); +} + +/// Check that the tree is still ordered after inserting a value smaller than +/// the smallest in the following tree. Run with `-bmc 2`. This rule is very +/// expensive, and one might have to set a higer timeout threshold. +/// The rule runs on the following tree: +/// +/// B:0 B:0 +/// / \ / \ +/// B:1 B:2 ---> B:3 B:2 +/// / / \ +/// R:3 R:4 R:1 +/// +#[rule] +pub fn rule_tree_is_ordered_after_insert_smallest_element() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let zero_index = 0 * TEST_BLOCK_WIDTH; + let one_index = 1 * TEST_BLOCK_WIDTH; + let two_index = 2 * TEST_BLOCK_WIDTH; + let three_index = 3 * TEST_BLOCK_WIDTH; + let four_index = 4 * TEST_BLOCK_WIDTH; + + let zero_val: u64 = nondet(); + let one_val: u64 = nondet_with(|x| *x <= zero_val); + let two_val: u64 = nondet_with(|x| *x >= zero_val); + let three_val: u64 = nondet_with(|x| *x <= one_val && *x <= zero_val); + + // 0 + *get_mut_helper(&mut data, zero_index) = mk_rb_node!( + one_index, + two_index, + NIL, + Color::Black, + TestOrder::new(zero_val) + ); + + // 1 + *get_mut_helper(&mut data, one_index) = mk_rb_node!( + three_index, + NIL, + zero_index, + Color::Black, + TestOrder::new(one_val) + ); + + // 2 + *get_mut_helper(&mut data, two_index) = + mk_rb_node!(NIL, NIL, zero_index, Color::Black, TestOrder::new(two_val)); + + // 3 + *get_mut_helper(&mut data, three_index) = + mk_rb_node!(NIL, NIL, one_index, Color::Red, TestOrder::new(three_val)); + + let mut tree: RedBlackTree = RedBlackTree::new(&mut data, zero_index, two_index); + + let four_val = nondet::(); + cvt_assume!(four_val < three_val); + + tree.insert(four_index, TestOrder::new(four_val)); + + let zero_index = tree.get_root_index(); + let zero_node = get_helper::>(&tree.data(), zero_index); + + // First layer of depth in the tree, left child + let three_index = tree.get_left_index::(zero_index); + let three_node = get_helper::>(&tree.data(), three_index); + cvt_assert!(three_node.value.order_id <= zero_node.value.order_id); + + // First layer of depth in the tree, right child + let two_index = tree.get_right_index::(zero_index); + let two_node = get_helper::>(&tree.data(), two_index); + cvt_assert!(two_node.value.order_id >= zero_node.value.order_id); + + // Second layer of depth in the tree, left child of the left child of the root + let four_index = tree.get_left_index::(three_index); + let four_node = get_helper::>(&tree.data(), four_index); + cvt_assert!(four_node.value.order_id <= three_node.value.order_id); + + // Second layer of depth in the tree, right child of the left child of the root + let one_index = tree.get_right_index::(three_index); + let one_node = get_helper::>(&tree.data(), one_index); + cvt_assert!(one_node.value.order_id >= three_node.value.order_id); + cvt_assert!(one_node.value.order_id <= zero_node.value.order_id); + + cvt_vacuity_check!(); +} + +// We verify that the `insert_fix` implementation matches the `RB-Delete-Fixup` +// implementation in the 4th edition of "Introduction to Algorithms", ISBN +// 026204630X. +// The pseudo-code of the function is at page 351. +// A screenshot of the procedure can be found at the following link: +// https://drive.google.com/file/d/19WqPdl9q0cRUJSu_Ifj__dgAYb5l0dDr/view +// While `RB-Insert-Fixup` has a while loop that fixes all the indices from the +// node that has to be fixed up to the root, the function `insert_fix` performs +// one single iteration of the loop, from the node at index `index_to_fix`, +// which corresponds to the node `z` in the pseudo-code. +// In `insert_fix` there is no need to write a while loop, as the funciton is +// called from `insert` inside of a while loop. +// What we prove is that, for each possible case in the `RB-Insert-Fixup` +// function, the implementation matches the expected behaviour. +// There are three possible cases, and each one of them has a specular rule +// depending on whether the parent of the node to fix is the left or the right +// child. + +/// Builds the following tree: +/// +/// ?:0 +/// / \ +/// ?:1 ?:2 +/// +macro_rules! build_tree_0 { + ($data: expr, $val_0: expr, $val_1: expr, $val_2: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + + // Nodes with defined values. + + // 0 + *get_mut_helper(&mut $data, index_0) = + mk_rb_node!(index_1, index_2, NIL, nondet(), TestOrder::new($val_0)); + + // 1 + *get_mut_helper(&mut $data, index_1) = mk_rb_node!( + nondet(), + nondet(), + index_0, + nondet(), + TestOrder::new($val_1) + ); + + // 2 + *get_mut_helper(&mut $data, index_2) = mk_rb_node!( + nondet(), + nondet(), + index_0, + nondet(), + TestOrder::new($val_2) + ); + + RedBlackTree::new(&mut $data, index_0, index_2) + }}; +} + +/// Check that `insert_fix` behaves as the reference implementation in case that +/// the node to be fixed has no parent, namely it is the root. +/// +/// ?:0 B:0 +/// / \ --> / \ +/// ?:1 ?:2 ?:1 ?:2 +/// +#[rule] +pub fn rule_insert_fix_matches_reference_no_parent() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + + let val_0: u64 = nondet(); + let val_1: u64 = nondet_with(|x| *x <= val_0); + let val_2: u64 = nondet_with(|x| *x >= val_0); + + let mut tree: RedBlackTree = build_tree_0!(data, val_0, val_1, val_2); + + // Node 3 is the one that has to be fixed, since its parent 1 is red as well + let next_to_fix_index = tree.certora_insert_fix(index_0); + + cvt_assert!(next_to_fix_index == NIL); + + // Assert that the colors and the indices of the nodes have been properly fixed. + + // The root is always black + cvt_assert!(tree.get_color::(index_0) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_0) == NIL); + cvt_assert!(tree.get_left_index::(index_0) == index_1); + cvt_assert!(tree.get_right_index::(index_0) == index_2); + + cvt_vacuity_check!(); +} + +/// Builds the following tree: +/// +/// ?:5 +/// | +/// ?:0 +/// / \ +/// R:1 R:2 +/// / \ / \ +/// R:3 B:4 ?:6 ?:7 +/// / \ / \ +/// ?:8 ?:9 ?:10 ?:11 +/// +macro_rules! build_tree_1 { + ($data: expr, $val_0: expr, $val_1: expr, $val_2: expr, $val_3: expr, $val_4: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + // Nodes with defined values. + + // 0 + *get_mut_helper(&mut $data, index_0) = + mk_rb_node!(index_1, index_2, index_5, nondet(), TestOrder::new($val_0)); + + // 1 + *get_mut_helper(&mut $data, index_1) = mk_rb_node!( + index_3, + index_4, + index_0, + Color::Red, + TestOrder::new($val_1) + ); + + // 2 + *get_mut_helper(&mut $data, index_2) = mk_rb_node!( + index_6, + index_7, + index_0, + Color::Red, + TestOrder::new($val_2) + ); + + // 3 + *get_mut_helper(&mut $data, index_3) = mk_rb_node!( + index_8, + index_9, + index_1, + Color::Red, + TestOrder::new($val_3) + ); + + // 4 + *get_mut_helper(&mut $data, index_4) = mk_rb_node!( + index_10, + index_11, + index_1, + Color::Black, + TestOrder::new($val_4) + ); + + // Padding nodes. + + // 5 + *get_mut_helper(&mut $data, index_5) = mk_rb_node!( + index_0, + nondet(), + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 6 + *get_mut_helper(&mut $data, index_6) = mk_rb_node!( + nondet(), + nondet(), + index_2, + nondet(), + TestOrder::new(nondet()) + ); + + // 7 + *get_mut_helper(&mut $data, index_7) = mk_rb_node!( + nondet(), + nondet(), + index_2, + nondet(), + TestOrder::new(nondet()) + ); + + // 8 + *get_mut_helper(&mut $data, index_8) = mk_rb_node!( + nondet(), + nondet(), + index_3, + nondet(), + TestOrder::new(nondet()) + ); + + // 9 + *get_mut_helper(&mut $data, index_9) = mk_rb_node!( + nondet(), + nondet(), + index_3, + nondet(), + TestOrder::new(nondet()) + ); + + // 10 + *get_mut_helper(&mut $data, index_10) = mk_rb_node!( + nondet(), + nondet(), + index_4, + nondet(), + TestOrder::new(nondet()) + ); + + // 11 + *get_mut_helper(&mut $data, index_11) = mk_rb_node!( + nondet(), + nondet(), + index_4, + nondet(), + TestOrder::new(nondet()) + ); + + RedBlackTree::new(&mut $data, index_5, index_7) + }}; +} + +/// Check that `insert_fix` behaves as the reference implementation in case 1, +/// when the parent of the node that has to be fixed (3) is a left child. +/// +/// ?:5 ?:5 +/// | | +/// ?:0 R:0 +/// / \ / \ +/// R:1 R:2 --> B:1 B:2 +/// / \ / \ / \ / \ +/// R:3 B:4 ?:6 ?:7 R:3 B:4 ?:6 ?:7 +/// / \ / \ / \ / \ +/// ?:8 ?:9 ?:10 ?:11 ?:8 ?:9 ?:10 ?:11 +/// +#[rule] +pub fn rule_insert_fix_matches_reference_case1_left_child() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + let val_0: u64 = nondet(); + let val_1: u64 = nondet_with(|x| *x <= val_0); + let val_2: u64 = nondet_with(|x| *x >= val_0); + let val_3: u64 = nondet_with(|x| *x <= val_1); + let val_4: u64 = nondet_with(|x| *x >= val_1 && *x <= val_0); + + let mut tree: RedBlackTree = build_tree_1!(data, val_0, val_1, val_2, val_3, val_4); + + // Node 3 is the one that has to be fixed, since its parent 1 is red as well + let next_to_fix_index = tree.certora_insert_fix(index_3); + + cvt_assert!(next_to_fix_index == index_0); + + // Assert that the colors and the indices of the nodes have been properly fixed. + + cvt_assert!(tree.get_color::(index_0) == Color::Red); + cvt_assert!(tree.get_parent_index::(index_0) == index_5); + cvt_assert!(tree.get_left_index::(index_0) == index_1); + cvt_assert!(tree.get_right_index::(index_0) == index_2); + + cvt_assert!(tree.get_color::(index_1) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_1) == index_0); + cvt_assert!(tree.get_left_index::(index_1) == index_3); + cvt_assert!(tree.get_right_index::(index_1) == index_4); + + cvt_assert!(tree.get_color::(index_2) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_2) == index_0); + cvt_assert!(tree.get_left_index::(index_2) == index_6); + cvt_assert!(tree.get_right_index::(index_2) == index_7); + + cvt_assert!(tree.get_color::(index_3) == Color::Red); + cvt_assert!(tree.get_parent_index::(index_3) == index_1); + cvt_assert!(tree.get_left_index::(index_3) == index_8); + cvt_assert!(tree.get_right_index::(index_3) == index_9); + + cvt_assert!(tree.get_color::(index_4) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_4) == index_1); + cvt_assert!(tree.get_left_index::(index_4) == index_10); + cvt_assert!(tree.get_right_index::(index_4) == index_11); + + cvt_vacuity_check!(); +} + +/// Builds the following tree: +/// +/// ?:5 +/// | +/// B:0 +/// / \ +/// R:1 B:2 +/// / \ / \ +/// B:3 R:4 ?:6 ?:7 +/// / \ / \ +/// ?:8 ?:9 ?:10 ?:11 +/// +macro_rules! build_tree_2 { + ($data: expr, $val_0: expr, $val_1: expr, $val_2: expr, $val_3: expr, $val_4: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + // Nodes with defined values. + + // 0 + *get_mut_helper(&mut $data, index_0) = mk_rb_node!( + index_1, + index_2, + index_5, + Color::Black, + TestOrder::new($val_0) + ); + + // 1 + *get_mut_helper(&mut $data, index_1) = mk_rb_node!( + index_3, + index_4, + index_0, + Color::Red, + TestOrder::new($val_1) + ); + + // 2 + *get_mut_helper(&mut $data, index_2) = mk_rb_node!( + index_6, + index_7, + index_0, + Color::Black, + TestOrder::new($val_2) + ); + + // 3 + *get_mut_helper(&mut $data, index_3) = mk_rb_node!( + index_8, + index_9, + index_1, + Color::Black, + TestOrder::new($val_3) + ); + + // 4 + *get_mut_helper(&mut $data, index_4) = mk_rb_node!( + index_10, + index_11, + index_1, + Color::Red, + TestOrder::new($val_4) + ); + + // Padding nodes. + + // 5 + *get_mut_helper(&mut $data, index_5) = mk_rb_node!( + index_0, + nondet(), + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 6 + *get_mut_helper(&mut $data, index_6) = mk_rb_node!( + nondet(), + nondet(), + index_2, + nondet(), + TestOrder::new(nondet()) + ); + + // 7 + *get_mut_helper(&mut $data, index_7) = mk_rb_node!( + nondet(), + nondet(), + index_2, + nondet(), + TestOrder::new(nondet()) + ); + + // 8 + *get_mut_helper(&mut $data, index_8) = mk_rb_node!( + nondet(), + nondet(), + index_3, + nondet(), + TestOrder::new(nondet()) + ); + + // 9 + *get_mut_helper(&mut $data, index_9) = mk_rb_node!( + nondet(), + nondet(), + index_3, + nondet(), + TestOrder::new(nondet()) + ); + + // 10 + *get_mut_helper(&mut $data, index_10) = mk_rb_node!( + nondet(), + nondet(), + index_4, + nondet(), + TestOrder::new(nondet()) + ); + + // 11 + *get_mut_helper(&mut $data, index_11) = mk_rb_node!( + nondet(), + nondet(), + index_4, + nondet(), + TestOrder::new(nondet()) + ); + + RedBlackTree::new(&mut $data, index_5, index_7) + }}; +} + +/// Check that `insert_fix` behaves as the reference implementation in case 2, +/// when the parent of the node that has to be fixed (4) is a left child. +/// +/// ?:5 ?:5 +/// | | +/// B:0 B:4 +/// / \ --> / \ +/// R:1 B:2 R:1 R:0 +/// / \ / \ / \ / \ +/// B:3 R:4 ?:6 ?:7 B:3 ?:10 ?:11 B:2 +/// / \ / \ / \ / \ +/// ?:8 ?:9 ?:10 ?:11 ?:8 ?:9 ?:6 ?:7 +/// +#[rule] +pub fn rule_insert_fix_matches_reference_case2_left_child() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + let val_0: u64 = nondet(); + let val_1: u64 = nondet_with(|x| *x <= val_0); + let val_2: u64 = nondet_with(|x| *x >= val_0); + let val_3: u64 = nondet_with(|x| *x <= val_1); + let val_4: u64 = nondet_with(|x| *x >= val_1 && *x <= val_0); + + let mut tree: RedBlackTree = build_tree_2!(data, val_0, val_1, val_2, val_3, val_4); + + // Node 4 is the one that has to be fixed, since its parent 1 is red as well + let next_to_fix_index = tree.certora_insert_fix(index_4); + + cvt_assert!(next_to_fix_index == NIL); + + // Assert that the colors and the indices of the nodes have been properly fixed. + + cvt_assert!(tree.get_color::(index_0) == Color::Red); + cvt_assert!(tree.get_parent_index::(index_0) == index_4); + cvt_assert!(tree.get_left_index::(index_0) == index_11); + cvt_assert!(tree.get_right_index::(index_0) == index_2); + + cvt_assert!(tree.get_color::(index_1) == Color::Red); + cvt_assert!(tree.get_parent_index::(index_1) == index_4); + cvt_assert!(tree.get_left_index::(index_1) == index_3); + cvt_assert!(tree.get_right_index::(index_1) == index_10); + + cvt_assert!(tree.get_color::(index_2) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_2) == index_0); + cvt_assert!(tree.get_left_index::(index_2) == index_6); + cvt_assert!(tree.get_right_index::(index_2) == index_7); + + cvt_assert!(tree.get_color::(index_3) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_3) == index_1); + cvt_assert!(tree.get_left_index::(index_3) == index_8); + cvt_assert!(tree.get_right_index::(index_3) == index_9); + + cvt_assert!(tree.get_color::(index_4) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_4) == index_5); + cvt_assert!(tree.get_left_index::(index_4) == index_1); + cvt_assert!(tree.get_right_index::(index_4) == index_0); + + cvt_vacuity_check!(); +} + +/// Builds the following tree: +/// +/// ?:5 +/// | +/// B:0 +/// / \ +/// R:1 B:2 +/// / \ / \ +/// R:3 B:4 ?:6 ?:7 +/// / \ / \ +/// ?:8 ?:9 ?:10 ?:11 +/// +macro_rules! build_tree_3 { + ($data: expr, $val_0: expr, $val_1: expr, $val_2: expr, $val_3: expr, $val_4: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + // Nodes with defined values. + + // 0 + *get_mut_helper(&mut $data, index_0) = mk_rb_node!( + index_1, + index_2, + index_5, + Color::Black, + TestOrder::new($val_0) + ); + + // 1 + *get_mut_helper(&mut $data, index_1) = mk_rb_node!( + index_3, + index_4, + index_0, + Color::Red, + TestOrder::new($val_1) + ); + + // 2 + *get_mut_helper(&mut $data, index_2) = mk_rb_node!( + index_6, + index_7, + index_0, + Color::Black, + TestOrder::new($val_2) + ); + + // 3 + *get_mut_helper(&mut $data, index_3) = mk_rb_node!( + index_8, + index_9, + index_1, + Color::Red, + TestOrder::new($val_3) + ); + + // 4 + *get_mut_helper(&mut $data, index_4) = mk_rb_node!( + index_10, + index_11, + index_1, + Color::Black, + TestOrder::new($val_4) + ); + + // Padding nodes. + + // 5 + *get_mut_helper(&mut $data, index_5) = mk_rb_node!( + index_0, + nondet(), + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 6 + *get_mut_helper(&mut $data, index_6) = mk_rb_node!( + nondet(), + nondet(), + index_2, + nondet(), + TestOrder::new(nondet()) + ); + + // 7 + *get_mut_helper(&mut $data, index_7) = mk_rb_node!( + nondet(), + nondet(), + index_2, + nondet(), + TestOrder::new(nondet()) + ); + + // 8 + *get_mut_helper(&mut $data, index_8) = mk_rb_node!( + nondet(), + nondet(), + index_3, + nondet(), + TestOrder::new(nondet()) + ); + + // 9 + *get_mut_helper(&mut $data, index_9) = mk_rb_node!( + nondet(), + nondet(), + index_3, + nondet(), + TestOrder::new(nondet()) + ); + + // 10 + *get_mut_helper(&mut $data, index_10) = mk_rb_node!( + nondet(), + nondet(), + index_4, + nondet(), + TestOrder::new(nondet()) + ); + + // 11 + *get_mut_helper(&mut $data, index_11) = mk_rb_node!( + nondet(), + nondet(), + index_4, + nondet(), + TestOrder::new(nondet()) + ); + + RedBlackTree::new(&mut $data, index_5, index_7) + }}; +} + +/// Check that `insert_fix` behaves as the reference implementation in case 3, +/// when the parent of the node that has to be fixed (3) is a left child. +/// +/// ?:5 ?:5 +/// | | +/// B:0 B:1 +/// / \ --> / \ +/// R:1 B:2 R:3 R:0 +/// / \ / \ / \ / \ +/// R:3 B:4 ?:6 ?:7 ?:8 ?:9 B:4 B:2 +/// / \ / \ / \ / \ +/// ?:9 ?:9 ?:10 ?:11 ?:10 ?:11 ?:6 ?:7 +/// +#[rule] +pub fn rule_insert_fix_matches_reference_case3_left_child() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + let val_0: u64 = nondet(); + let val_1: u64 = nondet_with(|x| *x <= val_0); + let val_2: u64 = nondet_with(|x| *x >= val_0); + let val_3: u64 = nondet_with(|x| *x <= val_1); + let val_4: u64 = nondet_with(|x| *x >= val_1 && *x <= val_0); + + let mut tree: RedBlackTree = build_tree_3!(data, val_0, val_1, val_2, val_3, val_4); + + // Node 3 is the one that has to be fixed, since its parent 1 is red as well + let next_to_fix_index = tree.certora_insert_fix(index_3); + + cvt_assert!(next_to_fix_index == NIL); + + // Assert that the colors and the indices of the nodes have been properly fixed. + + cvt_assert!(tree.get_color::(index_0) == Color::Red); + cvt_assert!(tree.get_parent_index::(index_0) == index_1); + cvt_assert!(tree.get_left_index::(index_0) == index_4); + cvt_assert!(tree.get_right_index::(index_0) == index_2); + + cvt_assert!(tree.get_color::(index_1) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_1) == index_5); + cvt_assert!(tree.get_left_index::(index_1) == index_3); + cvt_assert!(tree.get_right_index::(index_1) == index_0); + + cvt_assert!(tree.get_color::(index_2) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_2) == index_0); + cvt_assert!(tree.get_left_index::(index_2) == index_6); + cvt_assert!(tree.get_right_index::(index_2) == index_7); + + cvt_assert!(tree.get_color::(index_3) == Color::Red); + cvt_assert!(tree.get_parent_index::(index_3) == index_1); + cvt_assert!(tree.get_left_index::(index_3) == index_8); + cvt_assert!(tree.get_right_index::(index_3) == index_9); + + cvt_assert!(tree.get_color::(index_4) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_4) == index_0); + cvt_assert!(tree.get_left_index::(index_4) == index_10); + cvt_assert!(tree.get_right_index::(index_4) == index_11); + + cvt_vacuity_check!(); +} + +/// Builds the following tree: +/// +/// ?:5 +/// | +/// B:0 +/// / \ +/// R:1 R:2 +/// / \ / \ +/// ?:6 ?:7 B:3 R:4 +/// / \ / \ +/// ?:8 ?:9 ?:10 ?:11 +/// +macro_rules! build_tree_4 { + ($data: expr, $val_0: expr, $val_1: expr, $val_2: expr, $val_3: expr, $val_4: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + // Nodes with defined values. + + // 0 + *get_mut_helper(&mut $data, index_0) = mk_rb_node!( + index_1, + index_2, + index_5, + Color::Black, + TestOrder::new($val_0) + ); + + // 1 + *get_mut_helper(&mut $data, index_1) = mk_rb_node!( + index_6, + index_7, + index_0, + Color::Red, + TestOrder::new($val_1) + ); + + // 2 + *get_mut_helper(&mut $data, index_2) = mk_rb_node!( + index_3, + index_4, + index_0, + Color::Red, + TestOrder::new($val_2) + ); + + // 3 + *get_mut_helper(&mut $data, index_3) = mk_rb_node!( + index_8, + index_9, + index_2, + Color::Black, + TestOrder::new($val_3) + ); + + // 4 + *get_mut_helper(&mut $data, index_4) = mk_rb_node!( + index_10, + index_11, + index_2, + Color::Red, + TestOrder::new($val_4) + ); + + // Padding nodes. + + // 5 + *get_mut_helper(&mut $data, index_5) = mk_rb_node!( + index_0, + nondet(), + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 6 + *get_mut_helper(&mut $data, index_6) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 7 + *get_mut_helper(&mut $data, index_7) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 8 + *get_mut_helper(&mut $data, index_8) = mk_rb_node!( + nondet(), + nondet(), + index_3, + nondet(), + TestOrder::new(nondet()) + ); + + // 9 + *get_mut_helper(&mut $data, index_9) = mk_rb_node!( + nondet(), + nondet(), + index_3, + nondet(), + TestOrder::new(nondet()) + ); + + // 10 + *get_mut_helper(&mut $data, index_10) = mk_rb_node!( + nondet(), + nondet(), + index_4, + nondet(), + TestOrder::new(nondet()) + ); + + // 11 + *get_mut_helper(&mut $data, index_11) = mk_rb_node!( + nondet(), + nondet(), + index_4, + nondet(), + TestOrder::new(nondet()) + ); + + RedBlackTree::new(&mut $data, index_5, index_11) + }}; +} + +/// Check that `insert_fix` behaves as the reference implementation in case 1, +/// when the parent of the node that has to be fixed (4) is a right child. +/// +/// ?:5 ?:5 +/// | | +/// B:0 R:0 +/// / \ / \ +/// R:1 R:2 --> B:1 B:2 +/// / \ / \ / \ / \ +/// ?:6 ?:7 B:3 R:4 ?:6 ?:7 B:3 R:4 +/// / \ / \ / \ / \ +/// ?:8 ?:9 ?:10 ?:11 ?:8 ?:9 ?:10 ?:11 +/// +#[rule] +pub fn rule_insert_fix_matches_reference_case1_right_child() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + let val_0: u64 = nondet(); + let val_1: u64 = nondet_with(|x| *x <= val_0); + let val_2: u64 = nondet_with(|x| *x >= val_0); + let val_3: u64 = nondet_with(|x| *x <= val_1 && *x <= val_0); + let val_4: u64 = nondet_with(|x| *x >= val_1); + + let mut tree: RedBlackTree = build_tree_4!(data, val_0, val_1, val_2, val_3, val_4); + + // Node 4 is the one that has to be fixed, since its parent 2 is red as well + let next_to_fix_index = tree.certora_insert_fix(index_4); + + cvt_assert!(next_to_fix_index == index_0); + + // Assert that the colors and the indices of the nodes have been properly fixed. + + cvt_assert!(tree.get_color::(index_0) == Color::Red); + cvt_assert!(tree.get_parent_index::(index_0) == index_5); + cvt_assert!(tree.get_left_index::(index_0) == index_1); + cvt_assert!(tree.get_right_index::(index_0) == index_2); + + cvt_assert!(tree.get_color::(index_1) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_1) == index_0); + cvt_assert!(tree.get_left_index::(index_1) == index_6); + cvt_assert!(tree.get_right_index::(index_1) == index_7); + + cvt_assert!(tree.get_color::(index_2) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_2) == index_0); + cvt_assert!(tree.get_left_index::(index_2) == index_3); + cvt_assert!(tree.get_right_index::(index_2) == index_4); + + cvt_assert!(tree.get_color::(index_3) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_3) == index_2); + cvt_assert!(tree.get_left_index::(index_3) == index_8); + cvt_assert!(tree.get_right_index::(index_3) == index_9); + + cvt_assert!(tree.get_color::(index_4) == Color::Red); + cvt_assert!(tree.get_parent_index::(index_4) == index_2); + cvt_assert!(tree.get_left_index::(index_4) == index_10); + cvt_assert!(tree.get_right_index::(index_4) == index_11); + + cvt_vacuity_check!(); +} + +/// Builds the following tree: +/// +/// ?:5 +/// | +/// B:0 +/// / \ +/// B:1 R:2 +/// / \ / \ +/// ?:6 ?:7 R:3 B:4 +/// / \ / \ +/// ?:8 ?:9 ?:10 ?:11 +/// +macro_rules! build_tree_5 { + ($data: expr, $val_0: expr, $val_1: expr, $val_2: expr, $val_3: expr, $val_4: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + // Nodes with defined values. + + // 0 + *get_mut_helper(&mut $data, index_0) = mk_rb_node!( + index_1, + index_2, + index_5, + Color::Black, + TestOrder::new($val_0) + ); + + // 1 + *get_mut_helper(&mut $data, index_1) = mk_rb_node!( + index_6, + index_7, + index_0, + Color::Black, + TestOrder::new($val_1) + ); + + // 2 + *get_mut_helper(&mut $data, index_2) = mk_rb_node!( + index_3, + index_4, + index_0, + Color::Red, + TestOrder::new($val_2) + ); + + // 3 + *get_mut_helper(&mut $data, index_3) = mk_rb_node!( + index_8, + index_9, + index_2, + Color::Red, + TestOrder::new($val_3) + ); + + // 4 + *get_mut_helper(&mut $data, index_4) = mk_rb_node!( + index_10, + index_11, + index_2, + Color::Black, + TestOrder::new($val_4) + ); + + // Padding nodes. + + // 5 + *get_mut_helper(&mut $data, index_5) = mk_rb_node!( + index_0, + nondet(), + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 6 + *get_mut_helper(&mut $data, index_6) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 7 + *get_mut_helper(&mut $data, index_7) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 8 + *get_mut_helper(&mut $data, index_8) = mk_rb_node!( + nondet(), + nondet(), + index_3, + nondet(), + TestOrder::new(nondet()) + ); + + // 9 + *get_mut_helper(&mut $data, index_9) = mk_rb_node!( + nondet(), + nondet(), + index_3, + nondet(), + TestOrder::new(nondet()) + ); + + // 10 + *get_mut_helper(&mut $data, index_10) = mk_rb_node!( + nondet(), + nondet(), + index_4, + nondet(), + TestOrder::new(nondet()) + ); + + // 11 + *get_mut_helper(&mut $data, index_11) = mk_rb_node!( + nondet(), + nondet(), + index_4, + nondet(), + TestOrder::new(nondet()) + ); + + RedBlackTree::new(&mut $data, index_5, index_7) + }}; +} + +/// Check that `insert_fix` behaves as the reference implementation in case 2, +/// when the parent of the node that has to be fixed (3) is a right child. +/// +/// ?:5 ?:5 +/// | | +/// B:0 B:3 +/// / \ / \ +/// B:1 R:2 --> R:0 R:2 +/// / \ / \ / \ / \ +/// ?:6 ?:7 R:3 B:4 B:1 ?:8 ?:9 B:4 +/// / \ / \ / \ / \ +/// ?:8 ?:9 ?:10 ?:11 ?:6 ?:7 ?:10 ?:11 +/// +#[rule] +pub fn rule_insert_fix_matches_reference_case2_right_child() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + let val_0: u64 = nondet(); + let val_1: u64 = nondet_with(|x| *x <= val_0); + let val_2: u64 = nondet_with(|x| *x >= val_0); + let val_3: u64 = nondet_with(|x| *x <= val_1 && *x >= val_0); + let val_4: u64 = nondet_with(|x| *x >= val_1); + + let mut tree: RedBlackTree = build_tree_5!(data, val_0, val_1, val_2, val_3, val_4); + + // Node 3 is the one that has to be fixed, since its parent 2 is red as well + let next_to_fix_index = tree.certora_insert_fix(index_3); + + cvt_assert!(next_to_fix_index == NIL); + + // Assert that the colors and the indices of the nodes have been properly fixed. + + cvt_assert!(tree.get_color::(index_0) == Color::Red); + cvt_assert!(tree.get_parent_index::(index_0) == index_3); + cvt_assert!(tree.get_left_index::(index_0) == index_1); + cvt_assert!(tree.get_right_index::(index_0) == index_8); + + cvt_assert!(tree.get_color::(index_1) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_1) == index_0); + cvt_assert!(tree.get_left_index::(index_1) == index_6); + cvt_assert!(tree.get_right_index::(index_1) == index_7); + + cvt_assert!(tree.get_color::(index_2) == Color::Red); + cvt_assert!(tree.get_parent_index::(index_2) == index_3); + cvt_assert!(tree.get_left_index::(index_2) == index_9); + cvt_assert!(tree.get_right_index::(index_2) == index_4); + + cvt_assert!(tree.get_color::(index_3) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_3) == index_5); + cvt_assert!(tree.get_left_index::(index_3) == index_0); + cvt_assert!(tree.get_right_index::(index_3) == index_2); + + cvt_assert!(tree.get_color::(index_4) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_4) == index_2); + cvt_assert!(tree.get_left_index::(index_4) == index_10); + cvt_assert!(tree.get_right_index::(index_4) == index_11); + + cvt_vacuity_check!(); +} + +/// Builds the following tree: +/// +/// ?:5 +/// | +/// B:0 +/// / \ +/// B:1 R:2 +/// / \ / \ +/// ?:6 ?:7 B:3 R:4 +/// / \ / \ +/// ?:8 ?:9 ?:10 ?:11 +/// +macro_rules! build_tree_6 { + ($data: expr, $val_0: expr, $val_1: expr, $val_2: expr, $val_3: expr, $val_4: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + // Nodes with defined values. + + // 0 + *get_mut_helper(&mut $data, index_0) = mk_rb_node!( + index_1, + index_2, + index_5, + Color::Black, + TestOrder::new($val_0) + ); + + // 1 + *get_mut_helper(&mut $data, index_1) = mk_rb_node!( + index_6, + index_7, + index_0, + Color::Black, + TestOrder::new($val_1) + ); + + // 2 + *get_mut_helper(&mut $data, index_2) = mk_rb_node!( + index_3, + index_4, + index_0, + Color::Red, + TestOrder::new($val_2) + ); + + // 3 + *get_mut_helper(&mut $data, index_3) = mk_rb_node!( + index_8, + index_9, + index_2, + Color::Black, + TestOrder::new($val_3) + ); + + // 4 + *get_mut_helper(&mut $data, index_4) = mk_rb_node!( + index_10, + index_11, + index_2, + Color::Red, + TestOrder::new($val_4) + ); + + // Padding nodes. + + // 5 + *get_mut_helper(&mut $data, index_5) = mk_rb_node!( + index_0, + nondet(), + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 6 + *get_mut_helper(&mut $data, index_6) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 7 + *get_mut_helper(&mut $data, index_7) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 8 + *get_mut_helper(&mut $data, index_8) = mk_rb_node!( + nondet(), + nondet(), + index_3, + nondet(), + TestOrder::new(nondet()) + ); + + // 9 + *get_mut_helper(&mut $data, index_9) = mk_rb_node!( + nondet(), + nondet(), + index_3, + nondet(), + TestOrder::new(nondet()) + ); + + // 10 + *get_mut_helper(&mut $data, index_10) = mk_rb_node!( + nondet(), + nondet(), + index_4, + nondet(), + TestOrder::new(nondet()) + ); + + // 11 + *get_mut_helper(&mut $data, index_11) = mk_rb_node!( + nondet(), + nondet(), + index_4, + nondet(), + TestOrder::new(nondet()) + ); + + RedBlackTree::new(&mut $data, index_5, index_7) + }}; +} + +/// Check that `insert_fix` behaves as the reference implementation in case 3, +/// when the parent of the node that has to be fixed (4) is a right child. +/// +/// ?:5 ?:5 +/// | | +/// B:0 B:2 +/// / \ / \ +/// B:1 R:2 --> R:0 R:4 +/// / \ / \ / \ / \ +/// ?:6 ?:7 B:3 R:4 B:1 B:3 ?:10 ?:11 +/// / \ / \ / \ / \ +/// ?:8 ?:9 ?:10 ?:11 ?:6 ?:7 ?:8 ?:9 +/// +#[rule] +pub fn rule_insert_fix_matches_reference_case3_right_child() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + let val_0: u64 = nondet(); + let val_1: u64 = nondet_with(|x| *x <= val_0); + let val_2: u64 = nondet_with(|x| *x >= val_0); + let val_3: u64 = nondet_with(|x| *x <= val_1 && *x <= val_0); + let val_4: u64 = nondet_with(|x| *x >= val_1); + + let mut tree: RedBlackTree = build_tree_6!(data, val_0, val_1, val_2, val_3, val_4); + + // Node 4 is the one that has to be fixed, since its parent 2 is red as well + let next_to_fix_index = tree.certora_insert_fix(index_4); + + cvt_assert!(next_to_fix_index == NIL); + + // Assert that the colors and the indices of the nodes have been properly fixed. + + cvt_assert!(tree.get_color::(index_0) == Color::Red); + cvt_assert!(tree.get_parent_index::(index_0) == index_2); + cvt_assert!(tree.get_left_index::(index_0) == index_1); + cvt_assert!(tree.get_right_index::(index_0) == index_3); + + cvt_assert!(tree.get_color::(index_1) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_1) == index_0); + cvt_assert!(tree.get_left_index::(index_1) == index_6); + cvt_assert!(tree.get_right_index::(index_1) == index_7); + + cvt_assert!(tree.get_color::(index_2) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_2) == index_5); + cvt_assert!(tree.get_left_index::(index_2) == index_0); + cvt_assert!(tree.get_right_index::(index_2) == index_4); + + cvt_assert!(tree.get_color::(index_3) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_3) == index_0); + cvt_assert!(tree.get_left_index::(index_3) == index_8); + cvt_assert!(tree.get_right_index::(index_3) == index_9); + + cvt_assert!(tree.get_color::(index_4) == Color::Red); + cvt_assert!(tree.get_parent_index::(index_4) == index_2); + cvt_assert!(tree.get_left_index::(index_4) == index_10); + cvt_assert!(tree.get_right_index::(index_4) == index_11); + + cvt_vacuity_check!(); +} + +/// Builds the following tree: +/// +/// C5:5 +/// | +/// C0:0 +/// / \ +/// C1:1 C2:2 +/// / \ / \ +/// C6:6 C7:7 C3:3 C4:4 +/// / \ / \ +/// C8:8 C9:9 C10:10 C11:11 +/// +macro_rules! build_tree_shape_1 { + ($data: expr, + $val_0: expr, + $val_1: expr, + $val_2: expr, + $val_3: expr, + $val_4: expr, + $c0: expr, + $c1: expr, + $c2: expr, + $c3: expr, + $c4: expr, + $c5: expr, + $c6: expr, + $c7: expr, + $c8: expr, + $c9: expr, + $c10: expr, + $c11: expr, + ) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + // Nodes with defined values. + + // 0 + *get_mut_helper(&mut $data, index_0) = + mk_rb_node!(index_1, index_2, index_5, $c0, TestOrder::new($val_0)); + + // 1 + *get_mut_helper(&mut $data, index_1) = + mk_rb_node!(index_6, index_7, index_0, $c1, TestOrder::new($val_1)); + + // 2 + *get_mut_helper(&mut $data, index_2) = + mk_rb_node!(index_3, index_4, index_0, $c2, TestOrder::new($val_2)); + + // 3 + *get_mut_helper(&mut $data, index_3) = + mk_rb_node!(index_8, index_9, index_2, $c3, TestOrder::new($val_3)); + + // 4 + *get_mut_helper(&mut $data, index_4) = + mk_rb_node!(index_10, index_11, index_2, $c4, TestOrder::new($val_4)); + + // Padding nodes. + + // 5 + *get_mut_helper(&mut $data, index_5) = + mk_rb_node!(index_0, nondet(), nondet(), $c5, TestOrder::new(nondet())); + + // 6 + *get_mut_helper(&mut $data, index_6) = + mk_rb_node!(nondet(), nondet(), index_1, $c6, TestOrder::new(nondet())); + + // 7 + *get_mut_helper(&mut $data, index_7) = + mk_rb_node!(nondet(), nondet(), index_1, $c7, TestOrder::new(nondet())); + + // 8 + *get_mut_helper(&mut $data, index_8) = + mk_rb_node!(nondet(), nondet(), index_3, $c8, TestOrder::new(nondet())); + + // 9 + *get_mut_helper(&mut $data, index_9) = + mk_rb_node!(nondet(), nondet(), index_3, $c9, TestOrder::new(nondet())); + + // 10 + *get_mut_helper(&mut $data, index_10) = + mk_rb_node!(nondet(), nondet(), index_4, $c10, TestOrder::new(nondet())); + + // 11 + *get_mut_helper(&mut $data, index_11) = + mk_rb_node!(nondet(), nondet(), index_4, $c11, TestOrder::new(nondet())); + + RedBlackTree::new(&mut $data, index_5, index_11) + }}; +} + +/// Builds the following tree: +/// +/// C5:5 +/// | +/// C0:0 +/// / \ +/// C1:1 C2:2 +/// / \ / \ +/// C3:3 C4:4 C6:6 C7:7 +/// / \ / \ +/// C8:8 C9:9 C10:10 C11:11 +/// +macro_rules! build_tree_shape_2 { + ($data: expr, + $val_0: expr, + $val_1: expr, + $val_2: expr, + $val_3: expr, + $val_4: expr, + $c0: expr, + $c1: expr, + $c2: expr, + $c3: expr, + $c4: expr, + $c5: expr, + $c6: expr, + $c7: expr, + $c8: expr, + $c9: expr, + $c10: expr, + $c11: expr, + ) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + // Nodes with defined values. + + // 0 + *get_mut_helper(&mut $data, index_0) = + mk_rb_node!(index_1, index_2, index_5, $c0, TestOrder::new($val_0)); + + // 1 + *get_mut_helper(&mut $data, index_1) = + mk_rb_node!(index_3, index_4, index_0, $c1, TestOrder::new($val_1)); + + // 2 + *get_mut_helper(&mut $data, index_2) = + mk_rb_node!(index_6, index_7, index_0, $c2, TestOrder::new($val_2)); + + // 3 + *get_mut_helper(&mut $data, index_3) = + mk_rb_node!(index_8, index_9, index_1, $c3, TestOrder::new($val_3)); + + // 4 + *get_mut_helper(&mut $data, index_4) = + mk_rb_node!(index_10, index_11, index_1, $c4, TestOrder::new($val_4)); + + // Padding nodes. + + // 5 + *get_mut_helper(&mut $data, index_5) = + mk_rb_node!(index_0, NIL, NIL, $c5, TestOrder::new(nondet())); + + // 6 + *get_mut_helper(&mut $data, index_6) = + mk_rb_node!(NIL, NIL, index_2, $c6, TestOrder::new(nondet())); + + // 7 + *get_mut_helper(&mut $data, index_7) = + mk_rb_node!(NIL, NIL, index_2, $c7, TestOrder::new(nondet())); + + // 8 + *get_mut_helper(&mut $data, index_8) = + mk_rb_node!(NIL, NIL, index_3, $c8, TestOrder::new(nondet())); + + // 9 + *get_mut_helper(&mut $data, index_9) = + mk_rb_node!(NIL, NIL, index_3, $c9, TestOrder::new(nondet())); + + // 10 + *get_mut_helper(&mut $data, index_10) = + mk_rb_node!(NIL, NIL, index_4, $c10, TestOrder::new(nondet())); + + // 11 + *get_mut_helper(&mut $data, index_11) = + mk_rb_node!(NIL, NIL, index_4, $c11, TestOrder::new(nondet())); + + RedBlackTree::new(&mut $data, index_5, index_11) + }}; +} + +// We verify that the `certora_remove_fix` implementation matches the `RB-Insert-Fixup` +// implementation in the 4th edition of "Introduction to Algorithms", ISBN +// 026204630X. +// The pseudo-code of the function is at page 339. +// A screenshot of the procedure can be found at the following link: +// https://drive.google.com/file/d/11Dz6GcUFcQuMhHs2e1dfGdkkmYzC8XcD/view +// While `RB-Delete-Fixup` has a while loop that fixes all the indices from the +// node that has to be fixed up to the root, the function `remove_fix` performs +// one single iteration of the loop, from the node at index `current_index`, +// which corresponds to the node `x` in the pseudo-code. +// In `remove_fix` there is no need to write a while loop, as the funciton is +// called from `remove` inside of a while loop. +// What we prove is that, for each possible case in the `RB-Delete-Fixup` +// function, the implementation matches the expected behaviour. +// There are four possible cases, and each one of them has a specular rule +// depending on whether the node to fix is the left or the right child. + +/// Check that `remove_fix` behaves as the reference implementation in case 1, +/// when the node that has to be fixed (1) is a left child. +/// +/// ?:5 ?:5 +/// | | +/// ?:0 B:2 +/// / \ / \ +/// B:1 R:2 --> R:0 ?:4 +/// / \ / \ / \ / \ +/// ?:6 ?:7 ?:3 ?:4 B:1 ?:3 ?:10 ?:11 +/// / \ / \ / \ / \ +/// ?:8 ?:9 ?:10 ?:11 ?:6 ?:7 ?:8 ?:9 +/// +#[rule] +pub fn rule_remove_fix_matches_reference_case1_left_child() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + let val_0: u64 = nondet(); + let val_1: u64 = nondet_with(|x| *x <= val_0); + let val_2: u64 = nondet_with(|x| *x >= val_0); + let val_3: u64 = nondet_with(|x| *x <= val_2 && *x >= val_0); + let val_4: u64 = nondet_with(|x| *x >= val_2); + + let zero_initial_color = nondet(); + let three_initial_color = nondet(); + let four_inial_color = nondet(); + + let mut tree: RedBlackTree = build_tree_shape_1!( + data, + val_0, + val_1, + val_2, + val_3, + val_4, + zero_initial_color, // 0 + Color::Black, // 1 + Color::Red, // 2 + three_initial_color, // 3 + four_inial_color, // 4 + nondet(), // 5 + nondet(), // 6 + nondet(), // 7 + nondet(), // 8 + nondet(), // 9 + nondet(), // 10 + nondet(), // 11 + ); + + let (next_to_fix_index, next_to_fix_index_parent) = tree.certora_remove_fix(index_1, index_0); + + // In the reference implementation they recolor the sibling and the parent, + // and then they perform a rotation. After that, they keep considering the + // other cases, since case 1 can be then reduced to one of the subsequent + // cases. + // This is not necessary in `remove_fix`, since the index that is returned + // is `index_1`, which will then be handled in the outer while loop in the + // function `remove`. + // Therefore, we check the state of case 1 after executing the lines 5-8 in + // the pseudocode. + cvt_assert!(next_to_fix_index == index_1); + cvt_assert!(next_to_fix_index_parent == index_0); + + cvt_assert!(tree.get_color::(index_0) == Color::Red); + cvt_assert!(tree.get_parent_index::(index_0) == index_2); + cvt_assert!(tree.get_left_index::(index_0) == index_1); + cvt_assert!(tree.get_right_index::(index_0) == index_3); + + cvt_assert!(tree.get_color::(index_1) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_1) == index_0); + cvt_assert!(tree.get_left_index::(index_1) == index_6); + cvt_assert!(tree.get_right_index::(index_1) == index_7); + + cvt_assert!(tree.get_color::(index_2) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_2) == index_5); + cvt_assert!(tree.get_left_index::(index_2) == index_0); + cvt_assert!(tree.get_right_index::(index_2) == index_4); + + cvt_assert!(tree.get_color::(index_3) == three_initial_color); + cvt_assert!(tree.get_parent_index::(index_3) == index_0); + cvt_assert!(tree.get_left_index::(index_3) == index_8); + cvt_assert!(tree.get_right_index::(index_3) == index_9); + + cvt_assert!(tree.get_color::(index_4) == four_inial_color); + cvt_assert!(tree.get_parent_index::(index_4) == index_2); + cvt_assert!(tree.get_left_index::(index_4) == index_10); + cvt_assert!(tree.get_right_index::(index_4) == index_11); + + cvt_vacuity_check!(); +} + +/// Check that `remove_fix` behaves as the reference implementation in case 1, +/// when the node that has to be fixed (2) is a right child. +/// +/// ?:5 ?:5 +/// | | +/// ?:0 B:1 +/// / \ / \ +/// R:1 B:2 --> ?:3 R:0 +/// / \ / \ / \ / \ +/// ?:3 ?:4 ?:6 ?:7 ?:8 ?:9 ?:4 B:2 +/// / \ / \ / \ / \ +/// ?:8 ?:9 ?:10 ?:11 ?:10 ?:11 ?:6 ?:7 +#[rule] +pub fn rule_remove_fix_matches_reference_case1_right_child() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + let val_0: u64 = nondet(); + let val_1: u64 = nondet_with(|x| *x <= val_0); + let val_2: u64 = nondet_with(|x| *x >= val_0); + let val_3: u64 = nondet_with(|x| *x <= val_2 && *x >= val_0); + let val_4: u64 = nondet_with(|x| *x >= val_2); + + let zero_initial_color = nondet(); + let three_initial_color = nondet(); + let four_inial_color = nondet(); + + let mut tree: RedBlackTree = build_tree_shape_2!( + data, + val_0, + val_1, + val_2, + val_3, + val_4, + zero_initial_color, // 0 + Color::Red, // 1 + Color::Black, // 2 + three_initial_color, // 3 + four_inial_color, // 4 + nondet(), // 5 + nondet(), // 6 + nondet(), // 7 + nondet(), // 8 + nondet(), // 9 + nondet(), // 10 + nondet(), // 11 + ); + + let (next_to_fix_index, next_to_fix_index_parent) = tree.certora_remove_fix(index_2, index_0); + + // In the reference implementation they recolor the sibling and the parent, + // and then they perform a rotation. After that, they keep considering the + // other cases, since case 1 can be then reduced to one of the subsequent + // cases. + // This is not necessary in `remove_fix`, since the index that is returned + // is `index_2`, which will then be handled in the outer while loop in the + // function `remove`. + // Therefore, we check the state of case 1 after executing the lines 26-29 + // in the pseudocode. + cvt_assert!(next_to_fix_index == index_2); + cvt_assert!(next_to_fix_index_parent == index_0); + + cvt_assert!(tree.get_color::(index_0) == Color::Red); + cvt_assert!(tree.get_parent_index::(index_0) == index_1); + cvt_assert!(tree.get_left_index::(index_0) == index_4); + cvt_assert!(tree.get_right_index::(index_0) == index_2); + + cvt_assert!(tree.get_color::(index_1) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_1) == index_5); + cvt_assert!(tree.get_left_index::(index_1) == index_3); + cvt_assert!(tree.get_right_index::(index_1) == index_0); + + cvt_assert!(tree.get_color::(index_2) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_2) == index_0); + cvt_assert!(tree.get_left_index::(index_2) == index_6); + cvt_assert!(tree.get_right_index::(index_2) == index_7); + + cvt_assert!(tree.get_color::(index_3) == three_initial_color); + cvt_assert!(tree.get_parent_index::(index_3) == index_1); + cvt_assert!(tree.get_left_index::(index_3) == index_8); + cvt_assert!(tree.get_right_index::(index_3) == index_9); + + cvt_assert!(tree.get_color::(index_4) == four_inial_color); + cvt_assert!(tree.get_parent_index::(index_4) == index_0); + cvt_assert!(tree.get_left_index::(index_4) == index_10); + cvt_assert!(tree.get_right_index::(index_4) == index_11); + + cvt_vacuity_check!(); +} + +/// Check that `remove_fix` behaves as the reference implementation in case 2, +/// when the node that has to be fixed (1) is a left child. +/// +/// ?:5 ?:5 +/// | | +/// ?:0 B:0 +/// / \ / \ +/// B:1 B:2 --> B:1 R:2 +/// / \ / \ / \ / \ +/// ?:6 ?:7 B:3 B:4 ?:6 ?:7 B:3 B:4 +/// / \ / \ / \ / \ +/// ?:8 ?:9 ?:10 ?:11 ?:8 ?:9 ?:10 ?:11 +/// +#[rule] +pub fn rule_remove_fix_matches_reference_case2_left_child() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + let val_0: u64 = nondet(); + let val_1: u64 = nondet_with(|x| *x <= val_0); + let val_2: u64 = nondet_with(|x| *x >= val_0); + let val_3: u64 = nondet_with(|x| *x <= val_1); + let val_4: u64 = nondet_with(|x| *x >= val_1 && *x >= val_0); + + let zero_initial_color = nondet(); + + let mut tree: RedBlackTree = build_tree_shape_1!( + data, + val_0, + val_1, + val_2, + val_3, + val_4, + zero_initial_color, // 0 + Color::Black, // 1 + Color::Black, // 2 + Color::Black, // 3 + Color::Black, // 4 + nondet(), // 5 + nondet(), // 6 + nondet(), // 7 + nondet(), // 8 + nondet(), // 9 + nondet(), // 10 + nondet(), // 11 + ); + + let (next_to_fix_index, next_to_fix_index_parent) = tree.certora_remove_fix(index_1, index_0); + + if zero_initial_color == Color::Red { + // If node 0 was red, the procedure should stop. + cvt_assert!(next_to_fix_index == NIL); + cvt_assert!(next_to_fix_index_parent == NIL); + } + if zero_initial_color == Color::Black { + // If node 0 was black, the procedure should continue on node 0. + cvt_assert!(next_to_fix_index == index_0); + cvt_assert!(next_to_fix_index_parent == index_5); + } + + // The color of node 0 is always black in the reference implementation. + // If node 0 was black, its color does not change in case 2 (lines 10-11). + // If node 0 was red, its color does not change in the while loop, but when + // the loop ends because the condition is false: node 0 is red. + // Then, before returning, the color of node 0 (corresponding to variable + // `x` in the pseudo-code) is set to black. + cvt_assert!(tree.get_color::(index_0) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_0) == index_5); + cvt_assert!(tree.get_left_index::(index_0) == index_1); + cvt_assert!(tree.get_right_index::(index_0) == index_2); + + cvt_assert!(tree.get_color::(index_1) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_1) == index_0); + cvt_assert!(tree.get_left_index::(index_1) == index_6); + cvt_assert!(tree.get_right_index::(index_1) == index_7); + + cvt_assert!(tree.get_color::(index_2) == Color::Red); + cvt_assert!(tree.get_parent_index::(index_2) == index_0); + cvt_assert!(tree.get_left_index::(index_2) == index_3); + cvt_assert!(tree.get_right_index::(index_2) == index_4); + + cvt_assert!(tree.get_color::(index_3) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_3) == index_2); + cvt_assert!(tree.get_left_index::(index_3) == index_8); + cvt_assert!(tree.get_right_index::(index_3) == index_9); + + cvt_assert!(tree.get_color::(index_4) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_4) == index_2); + cvt_assert!(tree.get_left_index::(index_4) == index_10); + cvt_assert!(tree.get_right_index::(index_4) == index_11); + + cvt_vacuity_check!(); +} + +/// Check that `remove_fix` behaves as the reference implementation in case 2, +/// when the node that has to be fixed (2) is a right child. +/// +/// ?:5 ?:5 +/// | | +/// ?:0 B:0 +/// / \ / \ +/// B:1 B:2 --> R:1 B:2 +/// / \ / \ / \ / \ +/// B:3 B:4 ?:6 ?:7 B:3 B:4 ?:6 ?:7 +/// / \ / \ / \ / \ +/// ?:8 ?:9 ?:10 ?:11 ?:8 ?:9 ?:10 ?:11 +#[rule] +pub fn rule_remove_fix_matches_reference_case2_right_child() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + let val_0: u64 = nondet(); + let val_1: u64 = nondet_with(|x| *x <= val_0); + let val_2: u64 = nondet_with(|x| *x >= val_0); + let val_3: u64 = nondet_with(|x| *x <= val_1); + let val_4: u64 = nondet_with(|x| *x >= val_1 && *x >= val_0); + + let zero_initial_color = nondet(); + + let mut tree: RedBlackTree = build_tree_shape_2!( + data, + val_0, + val_1, + val_2, + val_3, + val_4, + zero_initial_color, // 0 + Color::Black, // 1 + Color::Black, // 2 + Color::Black, // 3 + Color::Black, // 4 + nondet(), // 5 + nondet(), // 6 + nondet(), // 7 + nondet(), // 8 + nondet(), // 9 + nondet(), // 10 + nondet(), // 11 + ); + + let (next_to_fix_index, next_to_fix_index_parent) = tree.certora_remove_fix(index_2, index_0); + + if zero_initial_color == Color::Red { + // If node 0 was red, the procedure should stop. + cvt_assert!(next_to_fix_index == NIL); + cvt_assert!(next_to_fix_index_parent == NIL); + } + if zero_initial_color == Color::Black { + // If node 0 was black, the procedure should continue on node 0. + cvt_assert!(next_to_fix_index == index_0); + cvt_assert!(next_to_fix_index_parent == index_5); + } + + // The color of node 0 is always black in the reference implementation. + // If node 0 was black, its color does not change in case 2 (lines 30-31). + // If node 0 was red, its color does not change in the while loop, but when + // the loop ends because the condition is false: node 0 is red. + // Then, before returning, the color of node 0 (corresponding to variable + // `x` in the pseudo-code) is set to black. + cvt_assert!(tree.get_color::(index_0) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_0) == index_5); + cvt_assert!(tree.get_left_index::(index_0) == index_1); + cvt_assert!(tree.get_right_index::(index_0) == index_2); + + cvt_assert!(tree.get_color::(index_1) == Color::Red); + cvt_assert!(tree.get_parent_index::(index_1) == index_0); + cvt_assert!(tree.get_left_index::(index_1) == index_3); + cvt_assert!(tree.get_right_index::(index_1) == index_4); + + cvt_assert!(tree.get_color::(index_2) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_2) == index_0); + cvt_assert!(tree.get_left_index::(index_2) == index_6); + cvt_assert!(tree.get_right_index::(index_2) == index_7); + + cvt_assert!(tree.get_color::(index_3) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_3) == index_1); + cvt_assert!(tree.get_left_index::(index_3) == index_8); + cvt_assert!(tree.get_right_index::(index_3) == index_9); + + cvt_assert!(tree.get_color::(index_4) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_4) == index_1); + cvt_assert!(tree.get_left_index::(index_4) == index_10); + cvt_assert!(tree.get_right_index::(index_4) == index_11); + + cvt_vacuity_check!(); +} + +/// Check that `remove_fix` behaves as the reference implementation in case 3, +/// when the node that has to be fixed (1) is a left child. +/// +/// ?:5 ?:5 +/// | | +/// ?:0 ?:3 +/// / \ / \ +/// B:1 B:2 --> B:0 B:2 +/// / \ / \ / \ / \ +/// ?:6 ?:7 R:3 B:4 B:1 ?:8 ?:9 B:4 +/// / \ / \ / \ / \ +/// ?:8 ?:9 ?:10 ?:11 ?:6 ?:7 ?:10 ?:11 +/// +/// Observe that node 3 should have the initial color of node 0, which is what +/// currently fails. +#[rule] +pub fn rule_remove_fix_matches_reference_case3_left_child() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + let val_0: u64 = nondet(); + let val_1: u64 = nondet_with(|x| *x <= val_0); + let val_2: u64 = nondet_with(|x| *x >= val_0); + let val_3: u64 = nondet_with(|x| *x <= val_2 && *x >= val_0); + let val_4: u64 = nondet_with(|x| *x >= val_2); + + let zero_initial_color = nondet(); + + let mut tree: RedBlackTree = build_tree_shape_1!( + data, + val_0, + val_1, + val_2, + val_3, + val_4, + zero_initial_color, // 0 + Color::Black, // 1 + Color::Black, // 2 + Color::Red, // 3 + Color::Black, // 4 + nondet(), // 5 + nondet(), // 6 + nondet(), // 7 + nondet(), // 8 + nondet(), // 9 + nondet(), // 10 + nondet(), // 11 + ); + + let (next_to_fix_index, next_to_fix_index_parent) = tree.certora_remove_fix(index_1, index_0); + + // Nothing to fix in this case. + cvt_assert!(next_to_fix_index == NIL); + cvt_assert!(next_to_fix_index_parent == NIL); + + cvt_assert!(tree.get_color::(index_0) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_0) == index_3); + cvt_assert!(tree.get_left_index::(index_0) == index_1); + cvt_assert!(tree.get_right_index::(index_0) == index_8); + + cvt_assert!(tree.get_color::(index_1) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_1) == index_0); + cvt_assert!(tree.get_left_index::(index_1) == index_6); + cvt_assert!(tree.get_right_index::(index_1) == index_7); + + cvt_assert!(tree.get_color::(index_2) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_2) == index_3); + cvt_assert!(tree.get_left_index::(index_2) == index_9); + cvt_assert!(tree.get_right_index::(index_2) == index_4); + + cvt_assert!(tree.get_color::(index_3) == zero_initial_color); + cvt_assert!(tree.get_parent_index::(index_3) == index_5); + cvt_assert!(tree.get_left_index::(index_3) == index_0); + cvt_assert!(tree.get_right_index::(index_3) == index_2); + + cvt_assert!(tree.get_color::(index_4) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_4) == index_2); + cvt_assert!(tree.get_left_index::(index_4) == index_10); + cvt_assert!(tree.get_right_index::(index_4) == index_11); + + cvt_vacuity_check!(); +} + +/// Check that `remove_fix` behaves as the reference implementation in case 3, +/// when the node that has to be fixed (2) is a right child. +/// +/// ?:5 ?:5 +/// | | +/// ?:0 ?:4 +/// / \ / \ +/// B:1 B:2 --> B:1 B:0 +/// / \ / \ / \ / \ +/// B:3 R:4 ?:6 ?:7 B:3 ?:10 ?:11 B:2 +/// / \ / \ / \ / \ +/// ?:8 ?:9 ?:10 ?:11 ?:8 ?:9 ?:6 ?:7 +/// +/// Observe that node 4 should have the initial color of node 0, which is what +/// currently fails. +#[rule] +pub fn rule_remove_fix_matches_reference_case3_right_child() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + let val_0: u64 = nondet(); + let val_1: u64 = nondet_with(|x| *x <= val_0); + let val_2: u64 = nondet_with(|x| *x >= val_0); + let val_3: u64 = nondet_with(|x| *x <= val_1); + let val_4: u64 = nondet_with(|x| *x >= val_1 && *x >= val_0); + + let zero_initial_color = nondet(); + + let mut tree: RedBlackTree = build_tree_shape_2!( + data, + val_0, + val_1, + val_2, + val_3, + val_4, + zero_initial_color, // 0 + Color::Black, // 1 + Color::Black, // 2 + Color::Black, // 3 + Color::Red, // 4 + nondet(), // 5 + nondet(), // 6 + nondet(), // 7 + nondet(), // 8 + nondet(), // 9 + nondet(), // 10 + nondet(), // 11 + ); + + let (next_to_fix_index, next_to_fix_index_parent) = tree.certora_remove_fix(index_2, index_0); + + // Nothing to fix in this case. + cvt_assert!(next_to_fix_index == NIL); + cvt_assert!(next_to_fix_index_parent == NIL); + + cvt_assert!(tree.get_color::(index_0) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_0) == index_4); + cvt_assert!(tree.get_left_index::(index_0) == index_11); + cvt_assert!(tree.get_right_index::(index_0) == index_2); + + cvt_assert!(tree.get_color::(index_1) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_1) == index_4); + cvt_assert!(tree.get_left_index::(index_1) == index_3); + cvt_assert!(tree.get_right_index::(index_1) == index_10); + + cvt_assert!(tree.get_color::(index_2) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_2) == index_0); + cvt_assert!(tree.get_left_index::(index_2) == index_6); + cvt_assert!(tree.get_right_index::(index_2) == index_7); + + cvt_assert!(tree.get_color::(index_3) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_3) == index_1); + cvt_assert!(tree.get_left_index::(index_3) == index_8); + cvt_assert!(tree.get_right_index::(index_3) == index_9); + + cvt_assert!(tree.get_color::(index_4) == zero_initial_color); + cvt_assert!(tree.get_parent_index::(index_4) == index_5); + cvt_assert!(tree.get_left_index::(index_4) == index_1); + cvt_assert!(tree.get_right_index::(index_4) == index_0); + + cvt_vacuity_check!(); +} + +/// Check that `remove_fix` behaves as the reference implementation in case 4, +/// when the node that has to be fixed (1) is a left child. +/// +/// ?:5 ?:5 +/// | | +/// ?:0 ?:2 +/// / \ / \ +/// B:1 B:2 --> B:0 B:4 +/// / \ / \ / \ / \ +/// ?:6 ?:7 B:3 R:4 B:1 B:3 ?:10 ?:11 +/// / \ / \ / \ / \ +/// ?:8 ?:9 ?:10 ?:11 ?:6 ?:7 ?:8 ?:9 +/// +/// Observe that node 2 should have the initial color of node 0. +#[rule] +pub fn rule_remove_fix_matches_reference_case4_left_child() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + let val_0: u64 = nondet(); + let val_1: u64 = nondet_with(|x| *x <= val_0); + let val_2: u64 = nondet_with(|x| *x >= val_0); + let val_3: u64 = nondet_with(|x| *x <= val_2 && *x >= val_0); + let val_4: u64 = nondet_with(|x| *x >= val_2); + + let zero_initial_color = nondet(); + + let mut tree: RedBlackTree = build_tree_shape_1!( + data, + val_0, + val_1, + val_2, + val_3, + val_4, + zero_initial_color, // 0 + Color::Black, // 1 + Color::Black, // 2 + Color::Black, // 3 + Color::Red, // 4 + nondet(), // 5 + nondet(), // 6 + nondet(), // 7 + nondet(), // 8 + nondet(), // 9 + nondet(), // 10 + nondet(), // 11 + ); + + let (next_to_fix_index, next_to_fix_index_parent) = tree.certora_remove_fix(index_1, index_0); + + // Nothing to fix in this case. + cvt_assert!(next_to_fix_index == NIL); + cvt_assert!(next_to_fix_index_parent == NIL); + + cvt_assert!(tree.get_color::(index_0) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_0) == index_2); + cvt_assert!(tree.get_left_index::(index_0) == index_1); + cvt_assert!(tree.get_right_index::(index_0) == index_3); + + cvt_assert!(tree.get_color::(index_1) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_1) == index_0); + cvt_assert!(tree.get_left_index::(index_1) == index_6); + cvt_assert!(tree.get_right_index::(index_1) == index_7); + + cvt_assert!(tree.get_color::(index_2) == zero_initial_color); + cvt_assert!(tree.get_parent_index::(index_2) == index_5); + cvt_assert!(tree.get_left_index::(index_2) == index_0); + cvt_assert!(tree.get_right_index::(index_2) == index_4); + + cvt_assert!(tree.get_color::(index_3) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_3) == index_0); + cvt_assert!(tree.get_left_index::(index_3) == index_8); + cvt_assert!(tree.get_right_index::(index_3) == index_9); + + cvt_assert!(tree.get_color::(index_4) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_4) == index_2); + cvt_assert!(tree.get_left_index::(index_4) == index_10); + cvt_assert!(tree.get_right_index::(index_4) == index_11); + + cvt_vacuity_check!(); +} + +/// Check that `remove_fix` behaves as the reference implementation in case 3, +/// when the node that has to be fixed (2) is a right child. +/// +/// ?:5 ?:5 +/// | | +/// ?:0 ?:1 +/// / \ / \ +/// B:1 B:2 --> B:3 B:0 +/// / \ / \ / \ / \ +/// R:3 B:4 ?:6 ?:7 ?:8 ?:9 B:4 B:2 +/// / \ / \ / \ / \ +/// ?:8 ?:9 ?:10 ?:11 ?:10 ?:11 ?:6 ?:7 +/// +/// Observe that node 1 should have the initial color of node 0. +#[rule] +pub fn rule_remove_fix_matches_reference_case4_right_child() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + let index_8 = 8 * TEST_BLOCK_WIDTH; + let index_9 = 9 * TEST_BLOCK_WIDTH; + let index_10 = 10 * TEST_BLOCK_WIDTH; + let index_11 = 11 * TEST_BLOCK_WIDTH; + + let val_0: u64 = nondet(); + let val_1: u64 = nondet_with(|x| *x <= val_0); + let val_2: u64 = nondet_with(|x| *x >= val_0); + let val_3: u64 = nondet_with(|x| *x <= val_1); + let val_4: u64 = nondet_with(|x| *x >= val_1 && *x >= val_0); + + let zero_initial_color = nondet(); + + let mut tree: RedBlackTree = build_tree_shape_2!( + data, + val_0, + val_1, + val_2, + val_3, + val_4, + zero_initial_color, // 0 + Color::Black, // 1 + Color::Black, // 2 + Color::Red, // 3 + Color::Black, // 4 + nondet(), // 5 + nondet(), // 6 + nondet(), // 7 + nondet(), // 8 + nondet(), // 9 + nondet(), // 10 + nondet(), // 11 + ); + + let (next_to_fix_index, next_to_fix_index_parent) = tree.certora_remove_fix(index_2, index_0); + + // Nothing to fix in this case. + cvt_assert!(next_to_fix_index == NIL); + cvt_assert!(next_to_fix_index_parent == NIL); + + cvt_assert!(tree.get_color::(index_0) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_0) == index_1); + cvt_assert!(tree.get_left_index::(index_0) == index_4); + cvt_assert!(tree.get_right_index::(index_0) == index_2); + + cvt_assert!(tree.get_color::(index_1) == zero_initial_color); + cvt_assert!(tree.get_parent_index::(index_1) == index_5); + cvt_assert!(tree.get_left_index::(index_1) == index_3); + cvt_assert!(tree.get_right_index::(index_1) == index_0); + + cvt_assert!(tree.get_color::(index_2) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_2) == index_0); + cvt_assert!(tree.get_left_index::(index_2) == index_6); + cvt_assert!(tree.get_right_index::(index_2) == index_7); + + cvt_assert!(tree.get_color::(index_3) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_3) == index_1); + cvt_assert!(tree.get_left_index::(index_3) == index_8); + cvt_assert!(tree.get_right_index::(index_3) == index_9); + + cvt_assert!(tree.get_color::(index_4) == Color::Black); + cvt_assert!(tree.get_parent_index::(index_4) == index_0); + cvt_assert!(tree.get_left_index::(index_4) == index_10); + cvt_assert!(tree.get_right_index::(index_4) == index_11); + + cvt_vacuity_check!(); +} + +/// Check that `insert` correctly updates the `max_index` after inserting in an +/// empty tree. +#[rule] +pub fn rule_insert_updates_max_index_empty_tree() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let mut tree: RedBlackTree = RedBlackTree::new(&mut data, NIL, NIL); + + cvt_assert!(tree.max_index() == NIL); + + tree.insert(0, TestOrder::new(nondet::())); + + cvt_assert!(tree.max_index() == 0); + cvt_vacuity_check!(); +} + +/// Builds the following tree: +/// +/// B:0 +/// / \ +/// subtree1 B:1 +/// / +/// subtree2 +/// +/// The subtrees are arbitrary trees for more coverage. +macro_rules! build_tree_7 { + ($data: expr, $val_0: expr, $val_1: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + + // 0 + *get_mut_helper(&mut $data, index_0) = + mk_rb_node!(nondet(), index_1, NIL, Color::Black, TestOrder::new($val_0)); + + // 1 + *get_mut_helper(&mut $data, index_1) = + mk_rb_node!(nondet(), NIL, index_0, Color::Black, TestOrder::new($val_1)); + + RedBlackTree::new(&mut $data, index_0, index_1) + }}; +} + +/// Check that `insert` correctly updates the `max_index` after inserting the +/// max element is a non-empty tree. +#[rule] +pub fn rule_insert_updates_max_index_non_empty_tree_max() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let val_0: u64 = nondet(); + let val_1: u64 = nondet_with(|x| *x >= val_0); + let val_2: u64 = nondet_with(|x| *x > val_1); + + // Node 0 and 1 are black to speed up the verification (no rotations + // required by insert). + let mut tree: RedBlackTree = build_tree_7!(data, val_0, val_1); + + cvt_assert!(tree.max_index() == index_1); + tree.insert(index_2, TestOrder::new(val_2)); + + // Check that insert correctly updated the max index. + cvt_assert!(tree.max_index() == index_2); + + cvt_vacuity_check!(); +} + +/// Builds the following tree: +/// +/// B:0 +/// / \ +/// subtree1 B:1 +/// +/// The subtree is an arbitrary tree for more coverage. +macro_rules! build_tree_8 { + ($data: expr, $val_0: expr, $val_1: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + + // 0 + *get_mut_helper(&mut $data, index_0) = + mk_rb_node!(nondet(), index_1, NIL, Color::Black, TestOrder::new($val_0)); + + // 1 + *get_mut_helper(&mut $data, index_1) = + mk_rb_node!(NIL, NIL, index_0, Color::Black, TestOrder::new($val_1)); + + RedBlackTree::new(&mut $data, index_0, index_1) + }}; +} + +/// Check that `insert` correctly updates the `max_index` after inserting an +/// element that is not the max in a non-empty tree. +#[rule] +pub fn rule_insert_updates_max_index_non_empty_tree_not_max() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let val_0: u64 = nondet(); + let val_1: u64 = nondet_with(|x| *x >= val_0); + let val_2: u64 = nondet_with(|x| *x > val_0 && *x < val_1); + + // Node 0 and 1 are black to speed up the verification (no rotations + // required by insert). + let mut tree: RedBlackTree = build_tree_8!(data, val_0, val_1); + + cvt_assert!(tree.max_index() == index_1); + tree.insert(index_2, TestOrder::new(val_2)); + + // Check that insert did not update the max index. + cvt_assert!(tree.max_index() == index_1); + + cvt_vacuity_check!(); +} + +/// Builds the following tree: +/// +/// B:2 +/// / \ +/// subtree1 B:0 +/// / \ +/// subtree2 R:1 +/// +/// The subtrees are arbitrary trees for more coverage. +macro_rules! build_tree_9 { + ($data: expr, $val_0: expr, $val_1: expr, $val_2: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + + // 2 + *get_mut_helper(&mut $data, index_2) = mk_rb_node!( + nondet_with(|i: &DataIndex| *i != index_0 && *i != index_1), + index_0, + NIL, + Color::Black, + TestOrder::new($val_2) + ); + + // 0 + *get_mut_helper(&mut $data, index_0) = mk_rb_node!( + nondet_with(|i: &DataIndex| *i != index_2 && *i != index_1), + index_1, + index_2, + Color::Black, + TestOrder::new($val_0) + ); + + // 1 + *get_mut_helper(&mut $data, index_1) = + mk_rb_node!(NIL, NIL, index_0, Color::Red, TestOrder::new($val_1)); + + RedBlackTree::new(&mut $data, index_2, index_1) + }}; +} + +/// Check that `remove` correctly updates the `max_index` after removing the +/// only element in a 1-element tree. +#[rule] +pub fn rule_remove_updates_max_index_single_node_tree() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0; + + // 0 + *get_mut_helper(&mut data, index_0) = + mk_rb_node!(NIL, NIL, NIL, Color::Black, TestOrder::new(nondet())); + + let mut tree: RedBlackTree = RedBlackTree::new(&mut data, index_0, index_0); + + cvt_assert!(tree.max_index() == index_0); + + tree.remove_by_index(index_0); + + cvt_assert!(tree.max_index() == NIL); + cvt_vacuity_check!(); +} + +/// Check that `remove` correctly updates the `max_index` after removing the +/// max element. +#[rule] +pub fn rule_remove_updates_max_index_non_empty_tree_max() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let _index_2 = 2 * TEST_BLOCK_WIDTH; + let val_2: u64 = nondet(); + let val_0: u64 = nondet_with(|x| *x > val_2); + let val_1: u64 = nondet_with(|x| *x > val_0); + + // Node 0 and 1 are respectively black and red to speed up the verification + // (no rotations required by remove). + let mut tree: RedBlackTree = build_tree_9!(data, val_0, val_1, val_2); + + cvt_assert!(tree.max_index() == index_1); + tree.remove_by_index(index_1); + + // Check that remove correctly updated the max index. + cvt_assert!(tree.max_index() == index_0); + + cvt_vacuity_check!(); +} + +/// Builds the following tree: +/// +/// B:2 +/// / \ +/// subtree1 B:0 +/// \ +/// R:1 +/// +/// The subtrees are arbitrary trees for more coverage. +macro_rules! build_tree_9_1 { + ($data: expr, $val_0: expr, $val_1: expr, $val_2: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + + // 2 + *get_mut_helper(&mut $data, index_2) = mk_rb_node!( + nondet_with(|i: &DataIndex| *i != index_0 && *i != index_1), + index_0, + NIL, + Color::Black, + TestOrder::new($val_2) + ); + + // 0 + *get_mut_helper(&mut $data, index_0) = + mk_rb_node!(NIL, index_1, index_2, Color::Black, TestOrder::new($val_0)); + + // 1 + *get_mut_helper(&mut $data, index_1) = + mk_rb_node!(NIL, NIL, index_0, Color::Red, TestOrder::new($val_1)); + + RedBlackTree::new(&mut $data, index_2, index_1) + }}; +} + +/// Check that `remove` correctly updates the `max_index` after removing an +/// element which is not the max. +#[rule] +pub fn rule_remove_updates_max_index_non_empty_tree_not_max() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let _index_2 = 2 * TEST_BLOCK_WIDTH; + let val_2: u64 = nondet(); + let val_0: u64 = nondet_with(|x| *x > val_2); + let val_1: u64 = nondet_with(|x| *x > val_0); + + // Node 0 and 1 are respectively black and red to speed up the verification + // (no rotations required by remove). + let mut tree: RedBlackTree = build_tree_9_1!(data, val_0, val_1, val_2); + + cvt_assert!(tree.max_index() == index_1); + tree.remove_by_index(index_0); + + // Check that remove did not update the max index. + cvt_assert!(tree.max_index() == index_1); + + cvt_vacuity_check!(); +} + +macro_rules! build_tree_10 { + ($data: expr, $c1: expr, $c5: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + + // 0 + *get_mut_helper(&mut $data, index_0) = mk_rb_node!( + index_1, + nondet_with(|i: &DataIndex| *i != index_1), + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 1 + *get_mut_helper(&mut $data, index_1) = + mk_rb_node!(index_2, index_3, index_0, $c1, TestOrder::new(nondet())); + + // 2 + *get_mut_helper(&mut $data, index_2) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 3 + *get_mut_helper(&mut $data, index_3) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 4 + *get_mut_helper(&mut $data, index_4) = mk_rb_node!( + index_5, + nondet_with(|i: &DataIndex| *i != index_5), + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 5 + *get_mut_helper(&mut $data, index_5) = + mk_rb_node!(index_6, index_7, index_4, $c5, TestOrder::new(nondet())); + + // 6 + *get_mut_helper(&mut $data, index_6) = mk_rb_node!( + nondet(), + nondet(), + index_5, + nondet(), + TestOrder::new(nondet()) + ); + + // 7 + *get_mut_helper(&mut $data, index_7) = mk_rb_node!( + nondet(), + nondet(), + index_5, + nondet(), + TestOrder::new(nondet()) + ); + + RedBlackTree::new(&mut $data, index_0, index_7) + }}; +} + +// TODO: Remove the rules that are expected to fail +/// Checks that `swap_nodes` behaves as expected in the case that the two nodes +/// are internal nodes, and both are left children. +#[rule] +pub fn rule_swap_internal_nodes_left_children() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + + let initial_color_1: Color = nondet(); + let initial_color_5: Color = nondet(); + + let mut tree: RedBlackTree = build_tree_10!(data, initial_color_1, initial_color_5); + + tree.swap_node_with_successor::(index_1, index_5); + + cvt_assert!(tree.get_color::(index_1) == initial_color_5); + cvt_assert!(tree.get_color::(index_5) == initial_color_1); + + cvt_assert!(tree.get_parent_index::(index_1) == index_4); + cvt_assert!(tree.get_parent_index::(index_5) == index_0); + cvt_assert!(tree.get_left_index::(index_0) == index_5); + cvt_assert!(tree.get_left_index::(index_4) == index_1); + + cvt_assert!(tree.get_left_index::(index_1) == index_6); + cvt_assert!(tree.get_left_index::(index_5) == index_2); + cvt_assert!(tree.get_parent_index::(index_6) == index_1); + cvt_assert!(tree.get_parent_index::(index_2) == index_5); + + cvt_assert!(tree.get_right_index::(index_1) == index_7); + cvt_assert!(tree.get_right_index::(index_5) == index_3); + cvt_assert!(tree.get_parent_index::(index_7) == index_1); + cvt_assert!(tree.get_parent_index::(index_3) == index_5); + + cvt_vacuity_check!(); +} + +macro_rules! build_tree_11 { + ($data: expr, $c1: expr, $c5: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + + // 0 + *get_mut_helper(&mut $data, index_0) = mk_rb_node!( + nondet_with(|i: &DataIndex| *i != index_1), + index_1, + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 1 + *get_mut_helper(&mut $data, index_1) = + mk_rb_node!(index_2, index_3, index_0, $c1, TestOrder::new(nondet())); + + // 2 + *get_mut_helper(&mut $data, index_2) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 3 + *get_mut_helper(&mut $data, index_3) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 4 + *get_mut_helper(&mut $data, index_4) = mk_rb_node!( + nondet_with(|i: &DataIndex| *i != index_5), + index_5, + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 5 + *get_mut_helper(&mut $data, index_5) = + mk_rb_node!(index_6, index_7, index_4, $c5, TestOrder::new(nondet())); + + // 6 + *get_mut_helper(&mut $data, index_6) = mk_rb_node!( + nondet(), + nondet(), + index_5, + nondet(), + TestOrder::new(nondet()) + ); + + // 7 + *get_mut_helper(&mut $data, index_7) = mk_rb_node!( + nondet(), + nondet(), + index_5, + nondet(), + TestOrder::new(nondet()) + ); + + RedBlackTree::new(&mut $data, index_0, index_7) + }}; +} + +/// Checks that `swap_nodes` behaves as expected in the case that the two nodes +/// are internal nodes, and both are right children. +#[rule] +pub fn rule_swap_internal_nodes_right_children() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + + let initial_color_1: Color = nondet(); + let initial_color_5: Color = nondet(); + + let mut tree: RedBlackTree = build_tree_11!(data, initial_color_1, initial_color_5); + + tree.swap_node_with_successor::(index_1, index_5); + + cvt_assert!(tree.get_color::(index_1) == initial_color_5); + cvt_assert!(tree.get_color::(index_5) == initial_color_1); + + cvt_assert!(tree.get_parent_index::(index_1) == index_4); + cvt_assert!(tree.get_parent_index::(index_5) == index_0); + cvt_assert!(tree.get_right_index::(index_0) == index_5); + cvt_assert!(tree.get_right_index::(index_4) == index_1); + + cvt_assert!(tree.get_left_index::(index_1) == index_6); + cvt_assert!(tree.get_left_index::(index_5) == index_2); + cvt_assert!(tree.get_parent_index::(index_6) == index_1); + cvt_assert!(tree.get_parent_index::(index_2) == index_5); + + cvt_assert!(tree.get_right_index::(index_1) == index_7); + cvt_assert!(tree.get_right_index::(index_5) == index_3); + cvt_assert!(tree.get_parent_index::(index_7) == index_1); + cvt_assert!(tree.get_parent_index::(index_3) == index_5); + + cvt_vacuity_check!(); +} + +macro_rules! build_tree_12 { + ($data: expr, $c1: expr, $c5: expr) => {{ + let _index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + + // 1 + *get_mut_helper(&mut $data, index_1) = + mk_rb_node!(index_2, index_3, NIL, $c1, TestOrder::new(nondet())); + + // 2 + *get_mut_helper(&mut $data, index_2) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 3 + *get_mut_helper(&mut $data, index_3) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 4 + *get_mut_helper(&mut $data, index_4) = mk_rb_node!( + nondet_with(|i: &DataIndex| *i != index_5), + index_5, + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 5 + *get_mut_helper(&mut $data, index_5) = + mk_rb_node!(index_6, index_7, index_4, $c5, TestOrder::new(nondet())); + + // 6 + *get_mut_helper(&mut $data, index_6) = mk_rb_node!( + nondet(), + nondet(), + index_5, + nondet(), + TestOrder::new(nondet()) + ); + + // 7 + *get_mut_helper(&mut $data, index_7) = mk_rb_node!( + nondet(), + nondet(), + index_5, + nondet(), + TestOrder::new(nondet()) + ); + + RedBlackTree::new(&mut $data, index_1, index_7) + }}; +} + +/// Checks that `swap_nodes` behaves as expected in the case that the two nodes +/// are internal nodes, and the first one is the root. +#[rule] +pub fn rule_swap_internal_nodes_first_is_root() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let _index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + + let initial_color_1: Color = nondet(); + let initial_color_5: Color = nondet(); + + let mut tree: RedBlackTree = build_tree_12!(data, initial_color_1, initial_color_5); + + tree.swap_node_with_successor::(index_1, index_5); + + cvt_assert!(tree.get_color::(index_1) == initial_color_5); + cvt_assert!(tree.get_color::(index_5) == initial_color_1); + + cvt_assert!(tree.get_parent_index::(index_1) == index_4); + cvt_assert!(tree.get_parent_index::(index_5) == NIL); + cvt_assert!(tree.get_right_index::(index_4) == index_1); + cvt_assert!(tree.get_root_index() == index_5); + + cvt_assert!(tree.get_left_index::(index_1) == index_6); + cvt_assert!(tree.get_left_index::(index_5) == index_2); + cvt_assert!(tree.get_parent_index::(index_6) == index_1); + cvt_assert!(tree.get_parent_index::(index_2) == index_5); + + cvt_assert!(tree.get_right_index::(index_1) == index_7); + cvt_assert!(tree.get_right_index::(index_5) == index_3); + cvt_assert!(tree.get_parent_index::(index_7) == index_1); + cvt_assert!(tree.get_parent_index::(index_3) == index_5); + + cvt_vacuity_check!(); +} + +macro_rules! build_tree_13 { + ($data: expr, $c1: expr, $c5: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let _index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + + // 0 + *get_mut_helper(&mut $data, index_0) = mk_rb_node!( + nondet_with(|i: &DataIndex| *i != index_1), + index_1, + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 1 + *get_mut_helper(&mut $data, index_1) = + mk_rb_node!(index_2, index_3, index_0, $c1, TestOrder::new(nondet())); + + // 2 + *get_mut_helper(&mut $data, index_2) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 3 + *get_mut_helper(&mut $data, index_3) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 5 + *get_mut_helper(&mut $data, index_5) = + mk_rb_node!(index_6, index_7, NIL, $c5, TestOrder::new(nondet())); + + // 6 + *get_mut_helper(&mut $data, index_6) = mk_rb_node!( + nondet(), + nondet(), + index_5, + nondet(), + TestOrder::new(nondet()) + ); + + // 7 + *get_mut_helper(&mut $data, index_7) = mk_rb_node!( + nondet(), + nondet(), + index_5, + nondet(), + TestOrder::new(nondet()) + ); + + RedBlackTree::new(&mut $data, index_5, index_7) + }}; +} + +/// Checks that `swap_nodes` behaves as expected in the case that the two nodes +/// are internal nodes, and the second one is the root. +/// This rule does not pass because the code currently asserts that the second +/// node is not the root. +#[rule] +pub fn rule_swap_internal_nodes_second_is_root() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let _index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + + let initial_color_1: Color = nondet(); + let initial_color_5: Color = nondet(); + + let mut tree: RedBlackTree = build_tree_13!(data, initial_color_1, initial_color_5); + + tree.swap_node_with_successor::(index_1, index_5); + + cvt_assert!(tree.get_color::(index_1) == initial_color_5); + cvt_assert!(tree.get_color::(index_5) == initial_color_1); + + cvt_assert!(tree.get_parent_index::(index_1) == NIL); + cvt_assert!(tree.get_parent_index::(index_5) == index_0); + cvt_assert!(tree.get_right_index::(index_0) == index_5); + cvt_assert!(tree.get_root_index() == index_1); + + cvt_assert!(tree.get_left_index::(index_1) == index_6); + cvt_assert!(tree.get_left_index::(index_5) == index_2); + cvt_assert!(tree.get_parent_index::(index_6) == index_1); + cvt_assert!(tree.get_parent_index::(index_2) == index_5); + + cvt_assert!(tree.get_right_index::(index_1) == index_7); + cvt_assert!(tree.get_right_index::(index_5) == index_3); + cvt_assert!(tree.get_parent_index::(index_7) == index_1); + cvt_assert!(tree.get_parent_index::(index_3) == index_5); + + cvt_vacuity_check!(); +} + +macro_rules! build_tree_14 { + ($data: expr, $c1: expr, $c5: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let _index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + + // 0 + *get_mut_helper(&mut $data, index_0) = mk_rb_node!( + nondet_with(|i: &DataIndex| *i != index_1), + index_1, + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 1 + *get_mut_helper(&mut $data, index_1) = + mk_rb_node!(NIL, index_3, index_0, $c1, TestOrder::new(nondet())); + + // 3 + *get_mut_helper(&mut $data, index_3) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 4 + *get_mut_helper(&mut $data, index_4) = mk_rb_node!( + nondet_with(|i: &DataIndex| *i != index_5), + index_5, + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 5 + *get_mut_helper(&mut $data, index_5) = + mk_rb_node!(index_6, NIL, index_4, $c5, TestOrder::new(nondet())); + + // 6 + *get_mut_helper(&mut $data, index_6) = mk_rb_node!( + nondet(), + nondet(), + index_5, + nondet(), + TestOrder::new(nondet()) + ); + + RedBlackTree::new(&mut $data, index_0, index_7) + }}; +} + +/// Checks that `swap_nodes` behaves as expected in the case that the two nodes +/// have only one child, respectively the left and the right. +#[rule] +pub fn rule_swap_nodes_with_one_child_left_right() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let _index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let index_6 = 6 * TEST_BLOCK_WIDTH; + let _index_7 = 7 * TEST_BLOCK_WIDTH; + + let initial_color_1: Color = nondet(); + let initial_color_5: Color = nondet(); + + let mut tree: RedBlackTree = build_tree_14!(data, initial_color_1, initial_color_5); + + tree.swap_node_with_successor::(index_1, index_5); + + cvt_assert!(tree.get_color::(index_1) == initial_color_5); + cvt_assert!(tree.get_color::(index_5) == initial_color_1); + + cvt_assert!(tree.get_parent_index::(index_1) == index_4); + cvt_assert!(tree.get_parent_index::(index_5) == index_0); + cvt_assert!(tree.get_right_index::(index_0) == index_5); + cvt_assert!(tree.get_right_index::(index_4) == index_1); + + cvt_assert!(tree.get_left_index::(index_1) == index_6); + cvt_assert!(tree.get_left_index::(index_5) == NIL); + cvt_assert!(tree.get_parent_index::(index_6) == index_1); + + cvt_assert!(tree.get_right_index::(index_1) == NIL); + cvt_assert!(tree.get_right_index::(index_5) == index_3); + cvt_assert!(tree.get_parent_index::(index_3) == index_5); + + cvt_vacuity_check!(); +} + +macro_rules! build_tree_15 { + ($data: expr, $c1: expr, $c5: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let _index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let _index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + + // 0 + *get_mut_helper(&mut $data, index_0) = mk_rb_node!( + index_1, + nondet_with(|i: &DataIndex| *i != index_1), + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 1 + *get_mut_helper(&mut $data, index_1) = + mk_rb_node!(index_2, NIL, index_0, $c1, TestOrder::new(nondet())); + + // 2 + *get_mut_helper(&mut $data, index_2) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 4 + *get_mut_helper(&mut $data, index_4) = mk_rb_node!( + index_5, + nondet_with(|i: &DataIndex| *i != index_5), + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 5 + *get_mut_helper(&mut $data, index_5) = + mk_rb_node!(NIL, index_7, index_4, $c5, TestOrder::new(nondet())); + + // 7 + *get_mut_helper(&mut $data, index_7) = mk_rb_node!( + nondet(), + nondet(), + index_5, + nondet(), + TestOrder::new(nondet()) + ); + + RedBlackTree::new(&mut $data, index_0, index_7) + }}; +} + +/// Checks that `swap_nodes` behaves as expected in the case that the two nodes +/// have only one child, respectively the right and the left. +#[rule] +pub fn rule_swap_nodes_with_one_child_right_left() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let _index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let _index_6 = 6 * TEST_BLOCK_WIDTH; + let index_7 = 7 * TEST_BLOCK_WIDTH; + + let initial_color_1: Color = nondet(); + let initial_color_5: Color = nondet(); + + let mut tree: RedBlackTree = build_tree_15!(data, initial_color_1, initial_color_5); + + tree.swap_node_with_successor::(index_1, index_5); + + cvt_assert!(tree.get_color::(index_1) == initial_color_5); + cvt_assert!(tree.get_color::(index_5) == initial_color_1); + + cvt_assert!(tree.get_parent_index::(index_1) == index_4); + cvt_assert!(tree.get_parent_index::(index_5) == index_0); + cvt_assert!(tree.get_left_index::(index_0) == index_5); + cvt_assert!(tree.get_left_index::(index_4) == index_1); + + cvt_assert!(tree.get_left_index::(index_1) == NIL); + cvt_assert!(tree.get_left_index::(index_5) == index_2); + cvt_assert!(tree.get_parent_index::(index_2) == index_5); + + cvt_assert!(tree.get_right_index::(index_1) == index_7); + cvt_assert!(tree.get_right_index::(index_5) == NIL); + cvt_assert!(tree.get_parent_index::(index_7) == index_1); + + cvt_vacuity_check!(); +} + +macro_rules! build_tree_16 { + ($data: expr, $c1: expr, $c5: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let _index_2 = 2 * TEST_BLOCK_WIDTH; + let _index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let _index_6 = 6 * TEST_BLOCK_WIDTH; + let _index_7 = 7 * TEST_BLOCK_WIDTH; + + // 0 + *get_mut_helper(&mut $data, index_0) = mk_rb_node!( + index_1, + nondet_with(|i: &DataIndex| *i != index_1), + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 1 + *get_mut_helper(&mut $data, index_1) = + mk_rb_node!(NIL, NIL, index_0, $c1, TestOrder::new(nondet())); + + // 4 + *get_mut_helper(&mut $data, index_4) = mk_rb_node!( + index_5, + nondet_with(|i: &DataIndex| *i != index_5), + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 5 + *get_mut_helper(&mut $data, index_5) = + mk_rb_node!(NIL, NIL, index_4, $c5, TestOrder::new(nondet())); + + RedBlackTree::new(&mut $data, index_0, index_5) + }}; +} + +/// Checks that `swap_nodes` behaves as expected in the case that the two nodes +/// are leaves. +#[rule] +pub fn rule_swap_leaves() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let _index_2 = 2 * TEST_BLOCK_WIDTH; + let _index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + let _index_6 = 6 * TEST_BLOCK_WIDTH; + let _index_7 = 7 * TEST_BLOCK_WIDTH; + + let initial_color_1: Color = nondet(); + let initial_color_5: Color = nondet(); + + let mut tree: RedBlackTree = build_tree_16!(data, initial_color_1, initial_color_5); + + tree.swap_node_with_successor::(index_1, index_5); + + cvt_assert!(tree.get_color::(index_1) == initial_color_5); + cvt_assert!(tree.get_color::(index_5) == initial_color_1); + + cvt_assert!(tree.get_parent_index::(index_1) == index_4); + cvt_assert!(tree.get_parent_index::(index_5) == index_0); + cvt_assert!(tree.get_left_index::(index_0) == index_5); + cvt_assert!(tree.get_left_index::(index_4) == index_1); + + cvt_assert!(tree.get_left_index::(index_1) == NIL); + cvt_assert!(tree.get_left_index::(index_5) == NIL); + + cvt_assert!(tree.get_right_index::(index_1) == NIL); + cvt_assert!(tree.get_right_index::(index_5) == NIL); + + cvt_vacuity_check!(); +} + +macro_rules! build_tree_17 { + ($data: expr, $c1: expr, $c3: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + + // 0 + *get_mut_helper(&mut $data, index_0) = mk_rb_node!( + index_1, + nondet_with(|i: &DataIndex| *i != index_1 && *i != index_3), + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 1 + *get_mut_helper(&mut $data, index_1) = + mk_rb_node!(index_2, index_3, index_0, $c1, TestOrder::new(nondet())); + + // 2 + *get_mut_helper(&mut $data, index_2) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 3 + *get_mut_helper(&mut $data, index_3) = + mk_rb_node!(index_4, index_5, index_1, $c3, TestOrder::new(nondet())); + + // 4 + *get_mut_helper(&mut $data, index_4) = mk_rb_node!( + nondet(), + nondet(), + index_3, + nondet(), + TestOrder::new(nondet()) + ); + + // 5 + *get_mut_helper(&mut $data, index_5) = mk_rb_node!( + nondet(), + nondet(), + index_3, + nondet(), + TestOrder::new(nondet()) + ); + + RedBlackTree::new(&mut $data, index_0, index_5) + }}; +} + +/// Checks that `swap_nodes` behaves as expected in the case that the two nodes +/// are parent and right child. +#[rule] +pub fn rule_swap_parent_right_child() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + + let initial_color_1: Color = nondet(); + let initial_color_3: Color = nondet(); + + let mut tree: RedBlackTree = build_tree_17!(data, initial_color_1, initial_color_3); + + tree.swap_node_with_successor::(index_1, index_3); + + cvt_assert!(tree.get_color::(index_1) == initial_color_3); + cvt_assert!(tree.get_color::(index_3) == initial_color_1); + + cvt_assert!(tree.get_parent_index::(index_1) == index_3); + cvt_assert!(tree.get_parent_index::(index_3) == index_0); + cvt_assert!(tree.get_left_index::(index_0) == index_3); + + cvt_assert!(tree.get_left_index::(index_1) == index_4); + cvt_assert!(tree.get_left_index::(index_3) == index_2); + cvt_assert!(tree.get_parent_index::(index_4) == index_1); + cvt_assert!(tree.get_parent_index::(index_2) == index_3); + + cvt_assert!(tree.get_right_index::(index_1) == index_5); + cvt_assert!(tree.get_right_index::(index_3) == index_1); + cvt_assert!(tree.get_parent_index::(index_5) == index_1); + cvt_assert!(tree.get_parent_index::(index_1) == index_3); + + cvt_vacuity_check!(); +} + +macro_rules! build_tree_18 { + ($data: expr, $c1: expr, $c3: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + + // 0 + *get_mut_helper(&mut $data, index_0) = mk_rb_node!( + nondet_with(|i: &DataIndex| *i != index_1 && *i != index_3), + index_1, + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 1 + *get_mut_helper(&mut $data, index_1) = + mk_rb_node!(index_3, index_2, index_0, $c1, TestOrder::new(nondet())); + + // 2 + *get_mut_helper(&mut $data, index_2) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 3 + *get_mut_helper(&mut $data, index_3) = + mk_rb_node!(index_4, index_5, index_1, $c3, TestOrder::new(nondet())); + + // 4 + *get_mut_helper(&mut $data, index_4) = mk_rb_node!( + nondet(), + nondet(), + index_3, + nondet(), + TestOrder::new(nondet()) + ); + + // 5 + *get_mut_helper(&mut $data, index_5) = mk_rb_node!( + nondet(), + nondet(), + index_3, + nondet(), + TestOrder::new(nondet()) + ); + + RedBlackTree::new(&mut $data, index_0, index_2) + }}; +} + +/// Checks that `swap_nodes` behaves as expected in the case that the two nodes +/// are parent and left child. +/// This rule is currently violated due to the non-properly handled edge case. +#[rule] +pub fn rule_swap_parent_left_child() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + + let initial_color_1: Color = nondet(); + let initial_color_3: Color = nondet(); + + let mut tree: RedBlackTree = build_tree_18!(data, initial_color_1, initial_color_3); + + tree.swap_node_with_successor::(index_1, index_3); + + cvt_assert!(tree.get_color::(index_1) == initial_color_3); + cvt_assert!(tree.get_color::(index_3) == initial_color_1); + + cvt_assert!(tree.get_parent_index::(index_1) == index_3); + cvt_assert!(tree.get_parent_index::(index_3) == index_0); + cvt_assert!(tree.get_right_index::(index_0) == index_3); + + cvt_assert!(tree.get_left_index::(index_1) == index_4); + cvt_assert!(tree.get_left_index::(index_3) == index_1); + cvt_assert!(tree.get_parent_index::(index_4) == index_1); + cvt_assert!(tree.get_parent_index::(index_1) == index_3); + + cvt_assert!(tree.get_right_index::(index_1) == index_5); + cvt_assert!(tree.get_right_index::(index_3) == index_2); + cvt_assert!(tree.get_parent_index::(index_5) == index_1); + cvt_assert!(tree.get_parent_index::(index_2) == index_3); + + cvt_vacuity_check!(); +} + +macro_rules! build_tree_19 { + ($data: expr, $c1: expr, $c3: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + + // 0 + *get_mut_helper(&mut $data, index_0) = mk_rb_node!( + index_3, + nondet_with(|i: &DataIndex| *i != index_1 && *i != index_3), + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 1 + *get_mut_helper(&mut $data, index_1) = + mk_rb_node!(index_4, index_5, index_3, $c1, TestOrder::new(nondet())); + + // 2 + *get_mut_helper(&mut $data, index_2) = mk_rb_node!( + nondet(), + nondet(), + index_3, + nondet(), + TestOrder::new(nondet()) + ); + + // 3 + *get_mut_helper(&mut $data, index_3) = + mk_rb_node!(index_2, index_1, index_0, $c3, TestOrder::new(nondet())); + + // 4 + *get_mut_helper(&mut $data, index_4) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 5 + *get_mut_helper(&mut $data, index_5) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + RedBlackTree::new(&mut $data, index_0, index_5) + }}; +} + +/// Checks that `swap_nodes` behaves as expected in the case that the two nodes +/// are right child and parent. +/// This rule is currently violated due to the non-properly handled edge case. +#[rule] +pub fn rule_swap_right_child_parent() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + + let initial_color_1: Color = nondet(); + let initial_color_3: Color = nondet(); + + let mut tree: RedBlackTree = build_tree_19!(data, initial_color_1, initial_color_3); + + tree.swap_node_with_successor::(index_1, index_3); + + cvt_assert!(tree.get_color::(index_1) == initial_color_3); + cvt_assert!(tree.get_color::(index_3) == initial_color_1); + + cvt_assert!(tree.get_parent_index::(index_1) == index_0); + cvt_assert!(tree.get_parent_index::(index_3) == index_1); + cvt_assert!(tree.get_left_index::(index_0) == index_1); + + cvt_assert!(tree.get_left_index::(index_1) == index_2); + cvt_assert!(tree.get_left_index::(index_3) == index_4); + cvt_assert!(tree.get_parent_index::(index_4) == index_3); + cvt_assert!(tree.get_parent_index::(index_2) == index_1); + + cvt_assert!(tree.get_right_index::(index_1) == index_3); + cvt_assert!(tree.get_right_index::(index_3) == index_5); + cvt_assert!(tree.get_parent_index::(index_5) == index_3); + cvt_assert!(tree.get_parent_index::(index_1) == index_0); + + cvt_vacuity_check!(); +} + +macro_rules! build_tree_20 { + ($data: expr, $c1: expr, $c3: expr) => {{ + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + + // 0 + *get_mut_helper(&mut $data, index_0) = mk_rb_node!( + nondet_with(|i: &DataIndex| *i != index_1 && *i != index_3), + index_3, + nondet(), + nondet(), + TestOrder::new(nondet()) + ); + + // 1 + *get_mut_helper(&mut $data, index_1) = + mk_rb_node!(index_4, index_5, index_3, $c1, TestOrder::new(nondet())); + + // 2 + *get_mut_helper(&mut $data, index_2) = mk_rb_node!( + nondet(), + nondet(), + index_3, + nondet(), + TestOrder::new(nondet()) + ); + + // 3 + *get_mut_helper(&mut $data, index_3) = + mk_rb_node!(index_1, index_2, index_0, $c3, TestOrder::new(nondet())); + + // 4 + *get_mut_helper(&mut $data, index_4) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + // 5 + *get_mut_helper(&mut $data, index_5) = mk_rb_node!( + nondet(), + nondet(), + index_1, + nondet(), + TestOrder::new(nondet()) + ); + + RedBlackTree::new(&mut $data, index_0, index_2) + }}; +} + +/// Checks that `swap_nodes` behaves as expected in the case that the two nodes +/// are left child and parent. +/// This rule is currently violated due to the non-properly handled edge case. +#[rule] +pub fn rule_swap_left_child_parent() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let acc_info = &acc_infos[0]; + let mut data = acc_info.data.borrow_mut(); + + let index_0 = 0 * TEST_BLOCK_WIDTH; + let index_1 = 1 * TEST_BLOCK_WIDTH; + let index_2 = 2 * TEST_BLOCK_WIDTH; + let index_3 = 3 * TEST_BLOCK_WIDTH; + let index_4 = 4 * TEST_BLOCK_WIDTH; + let index_5 = 5 * TEST_BLOCK_WIDTH; + + let initial_color_1: Color = nondet(); + let initial_color_3: Color = nondet(); + + let mut tree: RedBlackTree = build_tree_20!(data, initial_color_1, initial_color_3); + + tree.swap_node_with_successor::(index_1, index_3); + + cvt_assert!(tree.get_color::(index_1) == initial_color_3); + cvt_assert!(tree.get_color::(index_3) == initial_color_1); + + cvt_assert!(tree.get_parent_index::(index_1) == index_0); + cvt_assert!(tree.get_parent_index::(index_3) == index_1); + cvt_assert!(tree.get_right_index::(index_0) == index_1); + + cvt_assert!(tree.get_left_index::(index_1) == index_3); + cvt_assert!(tree.get_left_index::(index_3) == index_4); + cvt_assert!(tree.get_parent_index::(index_4) == index_3); + cvt_assert!(tree.get_parent_index::(index_2) == index_1); + + cvt_assert!(tree.get_right_index::(index_1) == index_2); + cvt_assert!(tree.get_right_index::(index_3) == index_5); + cvt_assert!(tree.get_parent_index::(index_5) == index_3); + cvt_assert!(tree.get_parent_index::(index_1) == index_0); + + cvt_vacuity_check!(); +} diff --git a/programs/manifest/src/certora/spec/swap_checks.rs b/programs/manifest/src/certora/spec/swap_checks.rs new file mode 100644 index 000000000..a02144829 --- /dev/null +++ b/programs/manifest/src/certora/spec/swap_checks.rs @@ -0,0 +1,150 @@ +use super::verification_utils::init_static; +use crate::{ + certora::spec::no_funds_loss_util::{ + cvt_assert_funds_invariants, cvt_assume_basic_market_preconditions, + cvt_assume_funds_invariants, record_all_balances_without_order, AllBalances, + }, + create_empty_market, cvt_static_initializer, +}; +use cvt::{cvt_assert, cvt_assume, cvt_vacuity_check}; +use cvt_macros::rule; +use nondet::*; +use solana_cvt::token::spl_token_account_get_amount; + +use crate::{ + program::{process_swap_core, SwapParams}, + state::MarketFixed, +}; +use hypertree::get_mut_helper; +use solana_program::account_info::AccountInfo; + +#[rule] +/// This rule can be further refined if additional specifications are given on the arguments to swap +pub fn rule_integrity_swap() { + init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let used_acc_infos: &[AccountInfo] = &acc_infos[..8]; + let market_info: &AccountInfo = &used_acc_infos[1]; + let trader_base_info: &AccountInfo = &used_acc_infos[2]; + let trader_quote_info: &AccountInfo = &used_acc_infos[3]; + let _base_vault_info: &AccountInfo = &used_acc_infos[4]; + let _quote_vault_info: &AccountInfo = &used_acc_infos[5]; + + // Create an empty market + create_empty_market!(market_info); + + let params = SwapParams::new(nondet(), nondet(), true, true); + let in_atoms: u64 = params.in_atoms; + let _out_atoms: u64 = params.out_atoms; + let trader_base_amount_old: u64 = spl_token_account_get_amount(trader_base_info); + let trader_quote_amount_old: u64 = spl_token_account_get_amount(trader_quote_info); + + process_swap_core(&crate::id(), &used_acc_infos, params).unwrap(); + + let trader_base_amount: u64 = spl_token_account_get_amount(trader_base_info); + let trader_quote_amount: u64 = spl_token_account_get_amount(trader_quote_info); + + // the trader pays with base + cvt_assert!(trader_base_amount <= trader_base_amount_old); + let trader_out: u64 = trader_base_amount_old - trader_base_amount; + + // the trader gets quote + cvt_assert!(trader_quote_amount >= trader_quote_amount_old); + let _trader_in: u64 = trader_quote_amount - trader_quote_amount_old; + + cvt_assert!(trader_out <= in_atoms); + + cvt_vacuity_check!() +} + +/// Parametric rule: no loss of funds for swap +fn rule_swap_check() { + cvt_static_initializer!(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let used_acc_infos: &[AccountInfo] = &acc_infos[..8]; + let trader: &AccountInfo = &used_acc_infos[0]; + let market: &AccountInfo = &used_acc_infos[1]; + let trader_base_token: &AccountInfo = &used_acc_infos[2]; + let trader_quote_token: &AccountInfo = &used_acc_infos[3]; + let vault_base_token: &AccountInfo = &used_acc_infos[4]; + let vault_quote_token: &AccountInfo = &used_acc_infos[5]; + // we only care about having a pubkey for the maker + let maker_trader: &AccountInfo = &acc_infos[9]; + + cvt_assume!(trader.key != vault_base_token.key); + cvt_assume!(trader.key != vault_quote_token.key); + cvt_assume!(trader_base_token.key != vault_base_token.key); + cvt_assume!(trader_quote_token.key != vault_quote_token.key); + + // -- basic market assumptions + cvt_assume_basic_market_preconditions( + market, + trader, + vault_base_token, + vault_quote_token, + maker_trader, + ); + + // -- record balances before swap + let old_balances: AllBalances = record_all_balances_without_order( + market, + vault_base_token, + vault_quote_token, + trader, + maker_trader, + ); + + // -- assume no loss of funds invariant + cvt_assume_funds_invariants(old_balances); + + let in_atoms: u64 = nondet(); + let out_atoms: u64 = nondet(); + // -- in_atoms does not overflow ghost aggregate + if IS_BASE { + cvt_assume!(in_atoms + .checked_add(old_balances.withdrawable_base) + .is_some()); + } else { + cvt_assume!(in_atoms + .checked_add(old_balances.withdrawable_quote) + .is_some()); + } + + let params: SwapParams = SwapParams::new(in_atoms, out_atoms, IS_BASE, IS_EXACT); + process_swap_core(&crate::id(), &used_acc_infos, params).unwrap(); + + let new_balances = record_all_balances_without_order( + market, + vault_base_token, + vault_quote_token, + trader, + maker_trader, + ); + + // -- check no loss of funds invariant + cvt_assert_funds_invariants(new_balances); + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_swap_base_exact() { + rule_swap_check::(); +} + +#[rule] +pub fn rule_swap_base_not_exact() { + rule_swap_check::(); +} + +#[rule] +pub fn rule_swap_quote_exact() { + rule_swap_check::(); +} + +#[rule] +pub fn rule_swap_quote_not_exact() { + rule_swap_check::(); +} diff --git a/programs/manifest/src/certora/spec/withdraw_checks.rs b/programs/manifest/src/certora/spec/withdraw_checks.rs new file mode 100644 index 000000000..c515aacc7 --- /dev/null +++ b/programs/manifest/src/certora/spec/withdraw_checks.rs @@ -0,0 +1,112 @@ +use cvt::{cvt_assert, cvt_assume, cvt_vacuity_check}; +use cvt_macros::rule; +use nondet::*; + +use crate::*; +use solana_program::account_info::AccountInfo; + +use solana_cvt::token::spl_token_account_get_amount; +use state::{cvt_assume_main_trader_has_seat, is_second_seat_taken, second_trader_pk}; + +use crate::{ + program::{ + get_mut_dynamic_account, + withdraw::{process_withdraw_core, WithdrawParams}, + }, + state::MarketRefMut, +}; + +// verifies when we use the fixed summary for the token2022 transfer, +// fails with a counterexample showing the transfer happening in the wrong direction otherwise, +// as long as we don't use the market initialization +#[rule] +pub fn rule_withdraw_withdraws() { + crate::certora::spec::verification_utils::init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let used_acc_infos: &[AccountInfo] = &acc_infos[..6]; + let trader_token: &AccountInfo = &used_acc_infos[2]; + let vault_token: &AccountInfo = &used_acc_infos[3]; + let trader: &AccountInfo = &used_acc_infos[0]; + let market: &AccountInfo = &used_acc_infos[1]; + let unrelated_trader: &AccountInfo = &acc_infos[7]; + + cvt_assume_main_trader_has_seat(trader.key); + + // -- trader and vault have different token accounts + cvt_assume!(trader_token.key != vault_token.key); + + cvt_assume!(trader.key != unrelated_trader.key); + cvt_assume!(unrelated_trader.key == second_trader_pk()); + cvt_assume!(is_second_seat_taken()); + + let (trader_base_old, trader_quote_old) = get_trader_balance!(market, trader.key); + let (unrelated_trader_base_old, unrelated_trader_quote_old) = + get_trader_balance!(market, unrelated_trader.key); + + let trader_amount_old: u64 = spl_token_account_get_amount(trader_token); + let vault_amount_old: u64 = spl_token_account_get_amount(vault_token); + + let amount: u64 = nondet(); + + process_withdraw_core( + &crate::id(), + &used_acc_infos, + WithdrawParams::new(amount, None), + ) + .unwrap(); + + let trader_amount: u64 = spl_token_account_get_amount(trader_token); + let vault_amount: u64 = spl_token_account_get_amount(vault_token); + + // These are violated for buggy transfer with token 2022 + cvt_assert!(trader_amount >= trader_amount_old); + cvt_assert!(vault_amount_old >= vault_amount); + let trader_diff: u64 = trader_amount - trader_amount_old; + let vault_diff: u64 = vault_amount_old - vault_amount; + + cvt_assert!(trader_diff == amount); + cvt_assert!(vault_diff == amount); + + let (trader_base, trader_quote) = get_trader_balance!(market, trader.key); + + let (unrelated_trader_base, unrelated_trader_quote) = + get_trader_balance!(market, unrelated_trader.key); + + cvt_assert!(trader_base_old >= trader_base); + cvt_assert!(trader_quote_old >= trader_quote); + let trader_base_diff: u64 = trader_base_old - trader_base; + let trader_quote_diff: u64 = trader_quote_old - trader_quote; + + // one of the diffs should be amount, the other zero + cvt_assert!(trader_base_diff + trader_quote_diff == amount); + cvt_assert!(trader_base_diff == 0 || trader_quote_diff == 0); + + cvt_assert!( + unrelated_trader_base == unrelated_trader_base_old + && unrelated_trader_quote == unrelated_trader_quote_old + ); + + cvt_vacuity_check!(); +} + +#[rule] +pub fn rule_withdraw_does_not_revert() { + crate::certora::spec::verification_utils::init_static(); + + let acc_infos: [AccountInfo; 16] = acc_infos_with_mem_layout!(); + let used_acc_infos: &[AccountInfo] = &acc_infos[..6]; + let trader: &AccountInfo = &used_acc_infos[0]; + + cvt_assume_main_trader_has_seat(trader.key); + + let amount: u64 = nondet(); + let result: ProgramResult = process_withdraw_core( + &crate::id(), + &used_acc_infos, + WithdrawParams::new(amount, None), + ); + cvt_assert!(result.is_ok()); + + cvt_vacuity_check!(); +} diff --git a/programs/manifest/src/certora/summaries/impact_base_atoms.rs b/programs/manifest/src/certora/summaries/impact_base_atoms.rs new file mode 100644 index 000000000..433583988 --- /dev/null +++ b/programs/manifest/src/certora/summaries/impact_base_atoms.rs @@ -0,0 +1,18 @@ +use crate::{ + quantities::{BaseAtoms, QuoteAtoms}, + state::{DerefOrBorrow, DynamicAccount, MarketFixed}, + validation::loaders::GlobalTradeAccounts, +}; +use solana_program::program_error::ProgramError; + +use nondet::*; + +/// Summary for impact_base_atoms +pub fn impact_base_atoms, Dynamic: DerefOrBorrow<[u8]>>( + _dynamic_account: &DynamicAccount, + _is_bid: bool, + _limit_quote_atoms: QuoteAtoms, + _global_trade_accounts_opts: &[Option; 2], +) -> Result { + Ok(nondet()) +} diff --git a/programs/manifest/src/certora/summaries/mod.rs b/programs/manifest/src/certora/summaries/mod.rs new file mode 100644 index 000000000..22230cc0f --- /dev/null +++ b/programs/manifest/src/certora/summaries/mod.rs @@ -0,0 +1,2 @@ +pub mod impact_base_atoms; +pub mod place_order; diff --git a/programs/manifest/src/certora/summaries/place_order.rs b/programs/manifest/src/certora/summaries/place_order.rs new file mode 100644 index 000000000..38afdcafa --- /dev/null +++ b/programs/manifest/src/certora/summaries/place_order.rs @@ -0,0 +1,102 @@ +use crate::{ + quantities::{BaseAtoms, QuoteAtoms, WrapperU64}, + state::{ + main_trader_index, second_trader_index, update_balance, AddOrderToMarketArgs, + AddOrderToMarketResult, MarketRefMut, + }, +}; +use hypertree::DataIndex; +use nondet::nondet; +use solana_program::program_error::ProgramError; + +/// This summary for place_order assumes that there is a matched order with a trader, +/// and its price is 1:1 +#[cfg(feature = "certora")] +pub fn place_fully_match_order_with_same_base_and_quote( + market: &mut MarketRefMut, + args: AddOrderToMarketArgs, +) -> Result { + let is_bid: bool = args.is_bid; + let num_base_atoms: BaseAtoms = args.num_base_atoms; + let base_traded: u64 = nondet(); + let quote_traded: u64 = nondet(); + let base_atoms_traded: BaseAtoms = BaseAtoms::new(base_traded); + let quote_atoms_traded: QuoteAtoms = QuoteAtoms::new(quote_traded); + + //////////////////////////////////////// + // -- Assumptions for this summary + //////////////////////////////////////// + // + // Any summary must satisfy this condition + cvt::cvt_assume!(base_atoms_traded <= num_base_atoms); + // Avoid underflow our ghost variables + cvt::cvt_assume!(market.fixed.orderbook_base_atoms >= base_atoms_traded); + cvt::cvt_assume!(market.fixed.orderbook_quote_atoms >= quote_atoms_traded); + // Condition specific to this summary: we fix price 1:1 + cvt::cvt_assume!(base_traded == quote_traded); + + let trader_index: DataIndex = main_trader_index(); + let maker_trader_index: DataIndex = second_trader_index(); + + update_balance( + market.fixed, + market.dynamic, + trader_index, + !is_bid, + false, + if is_bid { + quote_atoms_traded.into() + } else { + base_atoms_traded.into() + }, + )?; + + update_balance( + market.fixed, + market.dynamic, + maker_trader_index, + !is_bid, + true, + if is_bid { + quote_atoms_traded.into() + } else { + base_atoms_traded.into() + }, + )?; + + update_balance( + market.fixed, + market.dynamic, + trader_index, + is_bid, + true, + if is_bid { + base_atoms_traded.into() + } else { + quote_atoms_traded.into() + }, + )?; + + // This code depends on the price. This is fine for 1:1 price + // Otherwise, the amount in quote should be the amount in base multiplied by price + if is_bid { + market.fixed.orderbook_base_atoms = market + .fixed + .get_orderbook_base_atoms() + .saturating_sub(base_atoms_traded); + } else { + market.fixed.orderbook_quote_atoms = market + .fixed + .get_orderbook_quote_atoms() + .saturating_sub(quote_atoms_traded); + } + + let order_sequence_number: u64 = nondet(); + let order_index: DataIndex = nondet(); + Ok(AddOrderToMarketResult { + order_sequence_number, + order_index, + base_atoms_traded, + quote_atoms_traded, + }) +} diff --git a/programs/manifest/src/certora/utils.rs b/programs/manifest/src/certora/utils.rs new file mode 100644 index 000000000..c97445a44 --- /dev/null +++ b/programs/manifest/src/certora/utils.rs @@ -0,0 +1,242 @@ +#[macro_export] +macro_rules! create_empty_market { + ($market_acc_info:expr) => {{ + let empty_market_fixed: MarketFixed = MarketFixed::new_nondet(); + let mut market_bytes: std::cell::RefMut<&mut [u8]> = + $market_acc_info.data.try_borrow_mut().unwrap(); + *get_mut_helper::(*market_bytes, 0_u32) = empty_market_fixed; + }}; +} + +#[macro_export] +macro_rules! claim_seat { + ($market_acc_info:expr, $trader_key: expr) => {{ + let market_data: &mut std::cell::RefMut<&mut [u8]> = + &mut $market_acc_info.try_borrow_mut_data().unwrap(); + let mut dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); + dynamic_account.claim_seat($trader_key).unwrap(); + }}; +} + +#[macro_export] +macro_rules! get_trader_index { + ($market_acc_info:expr, $trader_key: expr) => {{ + let market_data: &mut std::cell::RefMut<&mut [u8]> = + &mut $market_acc_info.try_borrow_mut_data().unwrap(); + let dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); + dynamic_account.get_trader_index($trader_key) + }}; +} + +#[macro_export] +/// Return a pair of (base_atoms, quote_atoms) as u64 +macro_rules! get_trader_balance { + ($market_acc_info:expr, $trader_key: expr) => {{ + let market_data: &mut std::cell::RefMut<&mut [u8]> = + &mut $market_acc_info.try_borrow_mut_data().unwrap(); + let dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); + let (base_atoms, quote_atoms) = dynamic_account.get_trader_balance($trader_key); + (u64::from(base_atoms), u64::from(quote_atoms)) + }}; +} + +#[macro_export] +macro_rules! update_balance { + ($market_acc_info:expr, $trader_index: expr, $is_base: expr, $is_increase: expr, $amount: expr) => {{ + let market_data: &mut std::cell::RefMut<&mut [u8]> = + &mut $market_acc_info.try_borrow_mut_data().unwrap(); + let dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); + let DynamicAccount { fixed, dynamic } = dynamic_account; + crate::state::update_balance( + fixed, + dynamic, + $trader_index, + $is_base, + $is_increase, + $amount, + ) + .unwrap(); + }}; +} + +#[macro_export] +macro_rules! cvt_assert_is_nil { + ($e:expr) => { + cvt_assert!(is_nil!($e)) + }; +} + +#[macro_export] +macro_rules! deposit { + ($market_acc_info:expr, $trader_key: expr, $in_atoms: expr, $is_base_in: expr) => {{ + let market_data: &mut std::cell::RefMut<&mut [u8]> = + &mut $market_acc_info.try_borrow_mut_data().unwrap(); + let mut dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); + dynamic_account + .deposit($trader_key, $in_atoms, $is_base_in) + .unwrap(); + }}; +} + +#[macro_export] +/// Return the base token vault +macro_rules! get_base_vault { + ($market_acc_info:expr) => {{ + let market_data: &mut std::cell::RefMut<&mut [u8]> = + &mut $market_acc_info.try_borrow_mut_data().unwrap(); + let dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); + let DynamicAccount { fixed, .. } = dynamic_account; + *fixed.get_base_vault() + }}; +} +#[macro_export] + +/// Return the quote token vault +macro_rules! get_quote_vault { + ($market_acc_info:expr) => {{ + let market_data: &mut std::cell::RefMut<&mut [u8]> = + &mut $market_acc_info.try_borrow_mut_data().unwrap(); + let dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); + let DynamicAccount { fixed, .. } = dynamic_account; + *fixed.get_quote_vault() + }}; +} + +#[macro_export] +/// Return the withdrawable base token amount +macro_rules! get_withdrawable_base_atoms { + ($market_acc_info:expr) => {{ + let market_data: &mut std::cell::RefMut<&mut [u8]> = + &mut $market_acc_info.try_borrow_mut_data().unwrap(); + let dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); + let DynamicAccount { fixed, .. } = dynamic_account; + fixed.get_withdrawable_base_atoms().as_u64() + }}; +} +#[macro_export] +/// Return the withdrawable quote token amount +macro_rules! get_withdrawable_quote_atoms { + ($market_acc_info:expr) => {{ + let market_data: &mut std::cell::RefMut<&mut [u8]> = + &mut $market_acc_info.try_borrow_mut_data().unwrap(); + let dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); + let DynamicAccount { fixed, .. } = dynamic_account; + fixed.get_withdrawable_quote_atoms().as_u64() + }}; +} +#[macro_export] +/// Return the orderbook base token amount +macro_rules! get_orderbook_base_atoms { + ($market_acc_info:expr) => {{ + let market_data: &mut std::cell::RefMut<&mut [u8]> = + &mut $market_acc_info.try_borrow_mut_data().unwrap(); + let dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); + let DynamicAccount { fixed, .. } = dynamic_account; + fixed.get_orderbook_base_atoms().as_u64() + }}; +} +#[macro_export] +/// Return the orderbook quote token amount +macro_rules! get_orderbook_quote_atoms { + ($market_acc_info:expr) => {{ + let market_data: &mut std::cell::RefMut<&mut [u8]> = + &mut $market_acc_info.try_borrow_mut_data().unwrap(); + let dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); + let DynamicAccount { fixed, .. } = dynamic_account; + fixed.get_orderbook_quote_atoms().as_u64() + }}; +} + +#[macro_export] +macro_rules! get_order_atoms { + ($index:expr) => {{ + let dynamic: [u8; 8] = [0u8; 8]; + let order: &RestingOrder = get_helper_order(&dynamic, $index).get_value(); + order.get_orderbook_atoms().unwrap() + }}; +} + +#[macro_export] +macro_rules! rest_remaining { + ($market_acc_info:expr, + $args:expr, + $remaining_base_atoms: expr, + $order_sequence_number: expr, + $total_base_atoms_traded: expr, + $total_quote_atoms_traded: expr) => {{ + let market_data: &mut std::cell::RefMut<&mut [u8]> = + &mut $market_acc_info.try_borrow_mut_data().unwrap(); + let mut dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); + // let DynamicAccount { fixed, .. } = dynamic_account; + dynamic_account + .certora_rest_remaining( + $args, + $remaining_base_atoms, + $order_sequence_number, + $total_base_atoms_traded, + $total_quote_atoms_traded, + ) + .unwrap() + }}; +} + +#[macro_export] +macro_rules! cancel_order_by_index { + ( + $market_acc_info:expr, + $order_index:expr + ) => {{ + let market_data: &mut std::cell::RefMut<&mut [u8]> = + &mut $market_acc_info.try_borrow_mut_data().unwrap(); + let mut dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); + dynamic_account + .cancel_order_by_index($order_index, &[None, None]) + .unwrap(); + }}; +} + +#[macro_export] +macro_rules! place_single_order { + ( + $market_acc_info:expr, + $args:expr, + $remaining_base_atoms: expr, + $now_slot: expr, + $current_order_index: expr + ) => {{ + let market_data: &mut std::cell::RefMut<&mut [u8]> = + &mut $market_acc_info.try_borrow_mut_data().unwrap(); + let dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); + let DynamicAccount { fixed, dynamic } = dynamic_account; + + let mut ctx: AddSingleOrderCtx = + AddSingleOrderCtx::new($args, fixed, dynamic, $remaining_base_atoms, $now_slot); + + let res: AddOrderToMarketInnerResult = + ctx.place_single_order($current_order_index).unwrap(); + ( + res, + ctx.total_base_atoms_traded, + ctx.total_quote_atoms_traded, + ) + }}; +} + +extern "C" { + fn memhavoc_c(data: *mut u8, sz: usize) -> (); +} +pub fn memhavoc(data: *mut u8, size: usize) { + unsafe { + memhavoc_c(data, size); + } +} + +pub fn alloc_havoced() -> *mut T { + use std::alloc::Layout; + let layout = Layout::new::(); + unsafe { + let ptr = std::alloc::alloc(layout); + memhavoc(ptr, layout.size()); + ptr as *mut T + } +} diff --git a/programs/manifest/src/lib.rs b/programs/manifest/src/lib.rs index 6d6affec6..167b1770f 100644 --- a/programs/manifest/src/lib.rs +++ b/programs/manifest/src/lib.rs @@ -8,6 +8,9 @@ pub mod state; pub mod utils; pub mod validation; +#[cfg(feature = "certora")] +pub mod certora; + use hypertree::trace; use program::{ batch_update::process_batch_update, claim_seat::process_claim_seat, diff --git a/programs/manifest/src/logs.rs b/programs/manifest/src/logs.rs index 4d45e119b..5a7b16903 100644 --- a/programs/manifest/src/logs.rs +++ b/programs/manifest/src/logs.rs @@ -1,5 +1,3 @@ -use std::mem::size_of; - use bytemuck::{Pod, Zeroable}; use hypertree::PodBool; use shank::ShankAccount; @@ -18,14 +16,22 @@ use crate::{ /// because the goal of this program is to minimize the number of input /// accounts, so including the signer for the self CPI is not worth it. /// Also, be compatible with anchor parsing clients. + +#[cfg(not(feature = "certora"))] #[inline(never)] // ensure fresh stack frame pub fn emit_stack(e: T) -> Result<(), ProgramError> { // stack buffer, stack frames are 4kb let mut buffer: [u8; 3000] = [0u8; 3000]; buffer[..8].copy_from_slice(&T::discriminant()); - *bytemuck::from_bytes_mut::(&mut buffer[8..8 + size_of::()]) = e; + *bytemuck::from_bytes_mut::(&mut buffer[8..8 + std::mem::size_of::()]) = e; + + solana_program::log::sol_log_data(&[&buffer[..(std::mem::size_of::() + 8)]]); + Ok(()) +} - solana_program::log::sol_log_data(&[&buffer[..(size_of::() + 8)]]); +// Do not emit logs for formal verification. +#[cfg(feature = "certora")] +pub fn emit_stack(_e: T) -> Result<(), ProgramError> { Ok(()) } diff --git a/programs/manifest/src/program/error.rs b/programs/manifest/src/program/error.rs index b584fd9d5..6d6645503 100644 --- a/programs/manifest/src/program/error.rs +++ b/programs/manifest/src/program/error.rs @@ -56,6 +56,16 @@ impl From for ProgramError { } } +#[cfg(feature = "certora")] +#[macro_export] +macro_rules! require { + ($test:expr, $err:expr, $($arg:tt)*) => {{ + ::cvt::cvt_assume!($test); + Ok::<(), crate::ProgramError>(()) + }}; +} + +#[cfg(not(feature = "certora"))] #[macro_export] macro_rules! require { ($test:expr, $err:expr, $($arg:tt)*) => { diff --git a/programs/manifest/src/program/instruction_builders/batch_update_instruction.rs b/programs/manifest/src/program/instruction_builders/batch_update_instruction.rs index ac546a743..ecc33c7df 100644 --- a/programs/manifest/src/program/instruction_builders/batch_update_instruction.rs +++ b/programs/manifest/src/program/instruction_builders/batch_update_instruction.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "certora")] +use crate::program::batch_update::{CancelOrderParams, PlaceOrderParams}; +#[cfg(not(feature = "certora"))] use crate::{ program::{ batch_update::{BatchUpdateParams, CancelOrderParams, PlaceOrderParams}, @@ -5,8 +8,12 @@ use crate::{ }, validation::{get_global_address, get_global_vault_address, get_vault_address}, }; +#[cfg(not(feature = "certora"))] use borsh::BorshSerialize; use hypertree::DataIndex; +#[cfg(feature = "certora")] +use solana_program::{instruction::Instruction, pubkey::Pubkey}; +#[cfg(not(feature = "certora"))] use solana_program::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, @@ -15,6 +22,7 @@ use solana_program::{ // Token programs are needed for global orders with token22. Only include if // this is global or could match with global. Defaults to normal token program. +#[cfg(not(feature = "certora"))] pub fn batch_update_instruction( market: &Pubkey, payer: &Pubkey, @@ -68,3 +76,19 @@ pub fn batch_update_instruction( .concat(), } } + +#[cfg(feature = "certora")] +pub fn batch_update_instruction( + _market: &Pubkey, + _payer: &Pubkey, + _trader_index_hint: Option, + _cancels: Vec, + _orders: Vec, + _base_mint_opt: Option, + _base_mint_token_program_opt: Option, + _quote_mint_opt: Option, + _quote_mint_token_program_opt: Option, +) -> Instruction { + // Empty intentionally, just here so it compiles. + todo!() +} diff --git a/programs/manifest/src/program/processor/batch_update.rs b/programs/manifest/src/program/processor/batch_update.rs index 7483a0617..319de01a3 100644 --- a/programs/manifest/src/program/processor/batch_update.rs +++ b/programs/manifest/src/program/processor/batch_update.rs @@ -1,8 +1,8 @@ -use std::{cell::RefMut, mem::size_of}; +use std::cell::RefMut; use crate::{ logs::{emit_stack, CancelOrderLog, PlaceOrderLog}, - program::{get_trader_index_with_hint, ManifestError}, + program::get_trader_index_with_hint, quantities::{BaseAtoms, PriceConversionError, QuoteAtomsPerBaseAtom, WrapperU64}, require, state::{ @@ -12,13 +12,23 @@ use crate::{ validation::loaders::BatchUpdateContext, }; use borsh::{BorshDeserialize, BorshSerialize}; + use hypertree::{get_helper, trace, DataIndex, PodBool, RBNode}; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program::set_return_data, pubkey::Pubkey, + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, }; use super::{expand_market_if_needed, shared::get_mut_dynamic_account}; +use crate::validation::loaders::GlobalTradeAccounts; +#[cfg(feature = "certora")] +use { + crate::certora::mocks_batch_update::{mock_cancel_order, mock_place_order}, + early_panic::early_panic, + vectors::no_resizable_vec::NoResizableVec, +}; + #[derive(Debug, BorshDeserialize, BorshSerialize, Clone)] pub struct CancelOrderParams { order_sequence_number: u64, @@ -99,15 +109,23 @@ impl PlaceOrderParams { pub struct BatchUpdateParams { /// Optional hint for what index the trader's ClaimedSeat is at. pub trader_index_hint: Option, + #[cfg(not(feature = "certora"))] pub cancels: Vec, + #[cfg(feature = "certora")] + pub cancels: NoResizableVec, + #[cfg(not(feature = "certora"))] pub orders: Vec, + #[cfg(feature = "certora")] + pub orders: NoResizableVec, } impl BatchUpdateParams { pub fn new( trader_index_hint: Option, - cancels: Vec, - orders: Vec, + #[cfg(not(feature = "certora"))] cancels: Vec, + #[cfg(feature = "certora")] cancels: NoResizableVec, + #[cfg(not(feature = "certora"))] orders: Vec, + #[cfg(feature = "certora")] orders: NoResizableVec, ) -> Self { BatchUpdateParams { trader_index_hint, @@ -134,11 +152,67 @@ pub enum MarketDataTreeNodeType { } pub(crate) fn process_batch_update( - _program_id: &Pubkey, + program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8], +) -> ProgramResult { + let params: BatchUpdateParams = BatchUpdateParams::try_from_slice(data)?; + process_batch_update_core(program_id, accounts, params) +} + +#[cfg(not(feature = "certora"))] +fn batch_cancel_order( + dynamic_account: &mut MarketRefMut, + trader_index: DataIndex, + order_sequence_number: u64, + global_trade_accounts_opts: &[Option; 2], +) -> ProgramResult { + dynamic_account.cancel_order( + trader_index, + order_sequence_number, + &global_trade_accounts_opts, + ) +} + +#[cfg(feature = "certora")] +fn batch_cancel_order( + dynamic_account: &mut MarketRefMut, + trader_index: DataIndex, + order_sequence_number: u64, + global_trade_accounts_opts: &[Option; 2], +) -> ProgramResult { + mock_cancel_order( + &dynamic_account, + trader_index, + order_sequence_number, + &global_trade_accounts_opts, + ) +} + +#[cfg(not(feature = "certora"))] +fn batch_place_order( + dynamic_account: &mut MarketRefMut, + args: AddOrderToMarketArgs, +) -> Result { + dynamic_account.place_order(args) +} + +#[cfg(feature = "certora")] +fn batch_place_order( + dynamic_account: &mut MarketRefMut, + args: AddOrderToMarketArgs, +) -> Result { + mock_place_order(dynamic_account, args) +} + +#[cfg_attr(all(feature = "certora", not(feature = "certora-test")), early_panic)] +pub(crate) fn process_batch_update_core( + _program_id: &Pubkey, + accounts: &[AccountInfo], + params: BatchUpdateParams, ) -> ProgramResult { let batch_update_context: BatchUpdateContext = BatchUpdateContext::load(accounts)?; + let BatchUpdateContext { market, payer, @@ -150,7 +224,7 @@ pub(crate) fn process_batch_update( trader_index_hint, cancels, orders, - } = BatchUpdateParams::try_from_slice(data)?; + } = params; let current_slot: Option = Some(get_now_slot()); @@ -158,6 +232,7 @@ pub(crate) fn process_batch_update( let trader_index: DataIndex = { let market_data: &mut RefMut<&mut [u8]> = &mut market.try_borrow_mut_data()?; + let mut dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); let trader_index: DataIndex = get_trader_index_with_hint(trader_index_hint, &dynamic_account, &payer)?; @@ -169,7 +244,8 @@ pub(crate) fn process_batch_update( match cancel_order_params.order_index_hint() { None => { // Cancels must succeed otherwise we fail the tx. - dynamic_account.cancel_order( + batch_cancel_order( + &mut dynamic_account, trader_index, cancel_order_params.order_sequence_number(), &global_trade_accounts_opts, @@ -181,7 +257,7 @@ pub(crate) fn process_batch_update( // order owned by the payer inside the handler. require!( hinted_cancel_index % (MARKET_BLOCK_SIZE as DataIndex) == 0, - ManifestError::WrongIndexHintParams, + crate::program::ManifestError::WrongIndexHintParams, "Invalid cancel hint index {}", hinted_cancel_index, )?; @@ -192,7 +268,7 @@ pub(crate) fn process_batch_update( ) .get_payload_type() == MarketDataTreeNodeType::RestingOrder as u8, - ManifestError::WrongIndexHintParams, + crate::program::ManifestError::WrongIndexHintParams, "Invalid cancel hint index {}", hinted_cancel_index, )?; @@ -200,13 +276,13 @@ pub(crate) fn process_batch_update( dynamic_account.get_order_by_index(hinted_cancel_index); require!( trader_index == order.get_trader_index(), - ManifestError::WrongIndexHintParams, + crate::program::ManifestError::WrongIndexHintParams, "Invalid cancel hint index {}", hinted_cancel_index, )?; require!( cancel_order_params.order_sequence_number() == order.get_sequence_number(), - ManifestError::WrongIndexHintParams, + crate::program::ManifestError::WrongIndexHintParams, "Invalid cancel hint sequence number index {}", hinted_cancel_index, )?; @@ -225,7 +301,10 @@ pub(crate) fn process_batch_update( }; // Result is a vector of (order_sequence_number, data_index) + #[cfg(not(feature = "certora"))] let mut result: Vec<(u64, DataIndex)> = Vec::with_capacity(orders.len()); + #[cfg(feature = "certora")] + let mut result = NoResizableVec::<(u64, DataIndex)>::new(10); for place_order_params in orders { { let base_atoms: BaseAtoms = BaseAtoms::new(place_order_params.base_atoms()); @@ -237,8 +316,9 @@ pub(crate) fn process_batch_update( let market_data: &mut RefMut<&mut [u8]> = &mut market.try_borrow_mut_data()?; let mut dynamic_account: MarketRefMut = get_mut_dynamic_account(market_data); - let add_order_to_market_result: AddOrderToMarketResult = - dynamic_account.place_order(AddOrderToMarketArgs { + let add_order_to_market_result: AddOrderToMarketResult = batch_place_order( + &mut dynamic_account, + AddOrderToMarketArgs { market: *market.key, trader_index, num_base_atoms: base_atoms, @@ -248,7 +328,8 @@ pub(crate) fn process_batch_update( order_type, global_trade_accounts_opts: &global_trade_accounts_opts, current_slot, - })?; + }, + )?; let AddOrderToMarketResult { order_index, @@ -273,11 +354,17 @@ pub(crate) fn process_batch_update( expand_market_if_needed(&payer, &market, &system_program)?; } - let mut buffer: Vec = - Vec::with_capacity(size_of::() + result.len() * 2 * size_of::()); - let return_data: BatchUpdateReturn = BatchUpdateReturn { orders: result }; - return_data.serialize(&mut buffer).unwrap(); - set_return_data(&buffer[..]); + // Formal verification does not cover return values. + #[cfg(not(feature = "certora"))] + { + let mut buffer: Vec = Vec::with_capacity( + std::mem::size_of::() + + result.len() * 2 * std::mem::size_of::(), + ); + let return_data: BatchUpdateReturn = BatchUpdateReturn { orders: result }; + return_data.serialize(&mut buffer).unwrap(); + solana_program::program::set_return_data(&buffer[..]); + } Ok(()) } diff --git a/programs/manifest/src/program/processor/claim_seat.rs b/programs/manifest/src/program/processor/claim_seat.rs index c3119615b..ba8161a26 100644 --- a/programs/manifest/src/program/processor/claim_seat.rs +++ b/programs/manifest/src/program/processor/claim_seat.rs @@ -9,6 +9,10 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubke use super::shared::{expand_market_if_needed, get_mut_dynamic_account}; +#[cfg(feature = "certora")] +use early_panic::early_panic; + +#[cfg_attr(all(feature = "certora", not(feature = "certora-test")), early_panic)] pub(crate) fn process_claim_seat( _program_id: &Pubkey, accounts: &[AccountInfo], @@ -29,6 +33,7 @@ pub(crate) fn process_claim_seat( Ok(()) } +#[cfg_attr(all(feature = "certora", not(feature = "certora-test")), early_panic)] pub(crate) fn process_claim_seat_internal<'a, 'info>( market: &ManifestAccountInfo<'a, 'info, MarketFixed>, payer: &Signer<'a, 'info>, diff --git a/programs/manifest/src/program/processor/create_market.rs b/programs/manifest/src/program/processor/create_market.rs index 9a6ccfedf..547e33021 100644 --- a/programs/manifest/src/program/processor/create_market.rs +++ b/programs/manifest/src/program/processor/create_market.rs @@ -2,7 +2,7 @@ use std::{cell::Ref, mem::size_of}; use crate::{ logs::{emit_stack, CreateMarketLog}, - program::{expand_market_if_needed, invoke, ManifestError}, + program::{expand_market_if_needed, invoke}, require, state::MarketFixed, utils::create_account, @@ -44,7 +44,7 @@ pub(crate) fn process_create_market( require!( base_mint.info.key != quote_mint.info.key, - ManifestError::InvalidMarketParameters, + crate::program::ManifestError::InvalidMarketParameters, "Base and quote must be different", )?; diff --git a/programs/manifest/src/program/processor/deposit.rs b/programs/manifest/src/program/processor/deposit.rs index dcc7813da..ea1e93e9c 100644 --- a/programs/manifest/src/program/processor/deposit.rs +++ b/programs/manifest/src/program/processor/deposit.rs @@ -3,13 +3,20 @@ use std::cell::RefMut; use crate::{ logs::{emit_stack, DepositLog}, state::MarketRefMut, - validation::loaders::DepositContext, + validation::{ + loaders::DepositContext, MintAccountInfo, Signer, TokenAccountInfo, TokenProgram, + }, }; use borsh::{BorshDeserialize, BorshSerialize}; use hypertree::DataIndex; use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; -use super::{get_trader_index_with_hint, invoke, shared::get_mut_dynamic_account}; +use super::{get_trader_index_with_hint, shared::get_mut_dynamic_account}; + +#[cfg(feature = "certora")] +use early_panic::early_panic; +#[cfg(feature = "certora")] +use solana_cvt::token::{spl_token_2022_transfer, spl_token_transfer}; #[derive(BorshDeserialize, BorshSerialize)] pub struct DepositParams { @@ -27,15 +34,25 @@ impl DepositParams { } pub(crate) fn process_deposit( - _program_id: &Pubkey, + program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8], +) -> ProgramResult { + let params: DepositParams = DepositParams::try_from_slice(data)?; + process_deposit_core(program_id, accounts, params) +} + +#[cfg_attr(all(feature = "certora", not(feature = "certora-test")), early_panic)] +pub(crate) fn process_deposit_core( + _program_id: &Pubkey, + accounts: &[AccountInfo], + params: DepositParams, ) -> ProgramResult { let deposit_context: DepositContext = DepositContext::load(accounts)?; let DepositParams { amount_atoms, trader_index_hint, - } = DepositParams::try_from_slice(data)?; + } = params; // Due to transfer fees, this might not be what you expect. let mut deposited_amount_atoms: u64 = amount_atoms; @@ -57,32 +74,23 @@ pub(crate) fn process_deposit( if *vault.owner == spl_token_2022::id() { let before_vault_balance_atoms: u64 = vault.get_balance_atoms(); - invoke( - &spl_token_2022::instruction::transfer_checked( - token_program.key, - trader_token.key, - if is_base { - dynamic_account.fixed.get_base_mint() - } else { - dynamic_account.get_quote_mint() - }, - vault.key, - payer.key, - &[], - amount_atoms, - if is_base { - dynamic_account.fixed.get_base_mint_decimals() - } else { - dynamic_account.fixed.get_quote_mint_decimals() - }, - )?, - &[ - token_program.as_ref().clone(), - trader_token.as_ref().clone(), - mint.as_ref().clone(), - vault.as_ref().clone(), - payer.as_ref().clone(), - ], + spl_token_2022_transfer_from_trader_to_vault( + &token_program, + &trader_token, + Some(mint), + if is_base { + dynamic_account.fixed.get_base_mint() + } else { + dynamic_account.get_quote_mint() + }, + &vault, + &payer, + amount_atoms, + if is_base { + dynamic_account.fixed.get_base_mint_decimals() + } else { + dynamic_account.fixed.get_quote_mint_decimals() + }, )?; let after_vault_balance_atoms: u64 = vault.get_balance_atoms(); @@ -90,21 +98,12 @@ pub(crate) fn process_deposit( .checked_sub(before_vault_balance_atoms) .unwrap(); } else { - invoke( - &spl_token::instruction::transfer( - token_program.key, - trader_token.key, - vault.key, - payer.key, - &[], - amount_atoms, - )?, - &[ - token_program.as_ref().clone(), - trader_token.as_ref().clone(), - vault.as_ref().clone(), - payer.as_ref().clone(), - ], + spl_token_transfer_from_trader_to_vault( + &token_program, + &trader_token, + &vault, + &payer, + amount_atoms, )?; } @@ -125,3 +124,89 @@ pub(crate) fn process_deposit( Ok(()) } + +/** Transfer from base (quote) trader to base (quote) vault using SPL Token **/ +#[cfg(not(feature = "certora"))] +fn spl_token_transfer_from_trader_to_vault<'a, 'info>( + token_program: &TokenProgram<'a, 'info>, + trader_account: &TokenAccountInfo<'a, 'info>, + vault: &TokenAccountInfo<'a, 'info>, + payer: &Signer<'a, 'info>, + amount: u64, +) -> ProgramResult { + crate::program::invoke( + &spl_token::instruction::transfer( + token_program.key, + trader_account.key, + vault.key, + payer.key, + &[], + amount, + )?, + &[ + token_program.as_ref().clone(), + trader_account.as_ref().clone(), + vault.as_ref().clone(), + payer.as_ref().clone(), + ], + ) +} +#[cfg(feature = "certora")] +/** (Summary) Transfer from base (quote) trader to base (quote) vault using SPL Token **/ +fn spl_token_transfer_from_trader_to_vault<'a, 'info>( + _token_program: &TokenProgram<'a, 'info>, + trader_account: &TokenAccountInfo<'a, 'info>, + vault: &TokenAccountInfo<'a, 'info>, + payer: &Signer<'a, 'info>, + amount: u64, +) -> ProgramResult { + spl_token_transfer(trader_account.info, vault.info, payer.info, amount) +} + +/** Transfer from base (quote) trader to base (quote) vault using SPL Token 2022 **/ +#[cfg(not(feature = "certora"))] +fn spl_token_2022_transfer_from_trader_to_vault<'a, 'info>( + token_program: &TokenProgram<'a, 'info>, + trader_account: &TokenAccountInfo<'a, 'info>, + mint: Option>, + mint_pubkey: &Pubkey, + vault: &TokenAccountInfo<'a, 'info>, + payer: &Signer<'a, 'info>, + amount: u64, + decimals: u8, +) -> ProgramResult { + crate::program::invoke( + &spl_token_2022::instruction::transfer_checked( + token_program.key, + trader_account.key, + mint_pubkey, + vault.key, + payer.key, + &[], + amount, + decimals, + )?, + &[ + token_program.as_ref().clone(), + trader_account.as_ref().clone(), + vault.as_ref().clone(), + mint.unwrap().as_ref().clone(), + payer.as_ref().clone(), + ], + ) +} + +#[cfg(feature = "certora")] +/** (Summary) Transfer from base (quote) trader to base (quote) vault using SPL Token 2022 **/ +fn spl_token_2022_transfer_from_trader_to_vault<'a, 'info>( + _token_program: &TokenProgram<'a, 'info>, + trader_account: &TokenAccountInfo<'a, 'info>, + _mint: Option>, + _mint_pubkey: &Pubkey, + vault: &TokenAccountInfo<'a, 'info>, + payer: &Signer<'a, 'info>, + amount: u64, + _decimals: u8, +) -> ProgramResult { + spl_token_2022_transfer(trader_account.info, vault.info, payer.info, amount) +} diff --git a/programs/manifest/src/program/processor/global_clean.rs b/programs/manifest/src/program/processor/global_clean.rs index 6b2953a27..314e6e162 100644 --- a/programs/manifest/src/program/processor/global_clean.rs +++ b/programs/manifest/src/program/processor/global_clean.rs @@ -5,7 +5,7 @@ use hypertree::{get_helper, trace, DataIndex, RBNode}; use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; use crate::{ - program::{batch_update::MarketDataTreeNodeType, get_mut_dynamic_account, ManifestError}, + program::{batch_update::MarketDataTreeNodeType, get_mut_dynamic_account}, quantities::{GlobalAtoms, WrapperU64}, require, state::{utils::get_now_slot, GlobalRefMut, MarketRefMut, RestingOrder, MARKET_BLOCK_SIZE}, @@ -64,7 +64,7 @@ pub(crate) fn process_global_clean( // Sanity check on the order index require!( order_index % (MARKET_BLOCK_SIZE as DataIndex) == 0, - ManifestError::WrongIndexHintParams, + crate::program::ManifestError::WrongIndexHintParams, "Invalid order index {}", order_index, )?; @@ -72,7 +72,7 @@ pub(crate) fn process_global_clean( get_helper::>(&market_dynamic_account.dynamic, order_index); require!( resting_order_node.get_payload_type() == MarketDataTreeNodeType::RestingOrder as u8, - ManifestError::WrongIndexHintParams, + crate::program::ManifestError::WrongIndexHintParams, "Invalid order index {}", order_index, )?; @@ -88,7 +88,7 @@ pub(crate) fn process_global_clean( // Verify that the resting order uses the global account given. require!( *expected_global_mint == *global_mint, - ManifestError::InvalidClean, + crate::program::ManifestError::InvalidClean, "Wrong global provided", )?; @@ -116,7 +116,7 @@ pub(crate) fn process_global_clean( require!( is_expired || maker_global_balance.as_u64() < required_global_atoms, - ManifestError::InvalidClean, + crate::program::ManifestError::InvalidClean, "Ineligible clean order index {}", order_index, )?; diff --git a/programs/manifest/src/program/processor/global_evict.rs b/programs/manifest/src/program/processor/global_evict.rs index faa6a272a..cc3e2b042 100644 --- a/programs/manifest/src/program/processor/global_evict.rs +++ b/programs/manifest/src/program/processor/global_evict.rs @@ -6,7 +6,7 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubke use crate::{ global_vault_seeds_with_bump, logs::{emit_stack, GlobalDepositLog, GlobalEvictLog, GlobalWithdrawLog}, - program::{get_mut_dynamic_account, ManifestError}, + program::get_mut_dynamic_account, quantities::{GlobalAtoms, WrapperU64}, require, state::GlobalRefMut, @@ -58,7 +58,7 @@ pub(crate) fn process_global_evict( // Do verifications that this is a valid eviction. require!( evictee_balance < GlobalAtoms::new(amount_atoms), - ManifestError::InvalidEvict, + crate::program::ManifestError::InvalidEvict, "Evictee balance {} is more than evictor wants to deposit", evictee_balance.as_u64(), )?; diff --git a/programs/manifest/src/program/processor/shared.rs b/programs/manifest/src/program/processor/shared.rs index a218d0e81..7da9d24ed 100644 --- a/programs/manifest/src/program/processor/shared.rs +++ b/programs/manifest/src/program/processor/shared.rs @@ -4,7 +4,6 @@ use std::{ }; use crate::{ - program::ManifestError, require, state::{ claimed_seat::ClaimedSeat, constants::MARKET_BLOCK_SIZE, DynamicAccount, GlobalFixed, @@ -13,25 +12,23 @@ use crate::{ validation::{ManifestAccount, ManifestAccountInfo, Program, Signer}, }; use bytemuck::Pod; -use hypertree::{get_helper, get_mut_helper, trace, DataIndex, Get, RBNode}; +use hypertree::{get_helper, get_mut_helper, DataIndex, Get, RBNode}; +#[cfg(not(feature = "certora"))] +use solana_program::sysvar::Sysvar; use solana_program::{ - account_info::AccountInfo, - entrypoint::ProgramResult, - instruction::Instruction, - rent::Rent, - system_instruction, - sysvar::{slot_history::ProgramError, Sysvar}, + account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction, + sysvar::slot_history::ProgramError, }; use super::batch_update::MarketDataTreeNodeType; pub(crate) fn expand_market_if_needed<'a, 'info, T: ManifestAccount + Pod + Clone>( payer: &Signer<'a, 'info>, - manifest_account: &ManifestAccountInfo<'a, 'info, T>, + market_account_info: &ManifestAccountInfo<'a, 'info, T>, system_program: &Program<'a, 'info>, ) -> ProgramResult { let need_expand: bool = { - let market_data: &Ref<&mut [u8]> = &manifest_account.try_borrow_data()?; + let market_data: &Ref<&mut [u8]> = &market_account_info.try_borrow_data()?; let fixed: &MarketFixed = get_helper::(market_data, 0_u32); !fixed.has_free_block() }; @@ -39,7 +36,7 @@ pub(crate) fn expand_market_if_needed<'a, 'info, T: ManifestAccount + Pod + Clon if !need_expand { return Ok(()); } - expand_market(payer, manifest_account, system_program) + expand_market(payer, market_account_info, system_program) } pub(crate) fn expand_market<'a, 'info, T: ManifestAccount + Pod + Clone>( @@ -65,6 +62,16 @@ pub(crate) fn expand_global<'a, 'info, T: ManifestAccount + Pod + Clone>( Ok(()) } +#[cfg(feature = "certora")] +fn expand_dynamic<'a, 'info, T: ManifestAccount + Pod + Clone>( + _payer: &Signer<'a, 'info>, + _manifest_account: &ManifestAccountInfo<'a, 'info, T>, + _system_program: &Program<'a, 'info>, + _block_size: usize, +) -> ProgramResult { + Ok(()) +} +#[cfg(not(feature = "certora"))] fn expand_dynamic<'a, 'info, T: ManifestAccount + Pod + Clone>( payer: &Signer<'a, 'info>, manifest_account: &ManifestAccountInfo<'a, 'info, T>, @@ -76,7 +83,7 @@ fn expand_dynamic<'a, 'info, T: ManifestAccount + Pod + Clone>( let expandable_account: &AccountInfo = manifest_account.info; let new_size: usize = expandable_account.data_len() + block_size; - let rent: Rent = Rent::get()?; + let rent: solana_program::rent::Rent = solana_program::rent::Rent::get()?; let new_minimum_balance: u64 = rent.minimum_balance(new_size); let old_minimum_balance: u64 = rent.minimum_balance(expandable_account.data_len()); let lamports_diff: u64 = new_minimum_balance.saturating_sub(old_minimum_balance); @@ -84,13 +91,12 @@ fn expand_dynamic<'a, 'info, T: ManifestAccount + Pod + Clone>( let payer: &AccountInfo = payer.info; let system_program: &AccountInfo = system_program.info; - trace!( - "expand_dynamic-> transfer {} {:?}", - lamports_diff, - expandable_account.key - ); invoke( - &system_instruction::transfer(payer.key, expandable_account.key, lamports_diff), + &solana_program::system_instruction::transfer( + payer.key, + expandable_account.key, + lamports_diff, + ), &[ payer.clone(), expandable_account.clone(), @@ -98,15 +104,10 @@ fn expand_dynamic<'a, 'info, T: ManifestAccount + Pod + Clone>( ], )?; - trace!( - "expand_dynamic-> realloc {} {:?}", - new_size, - expandable_account.key - ); #[cfg(feature = "fuzz")] { solana_program::program::invoke( - &system_instruction::allocate(expandable_account.key, new_size as u64), + &solana_program::system_instruction::allocate(expandable_account.key, new_size as u64), &[expandable_account.clone(), system_program.clone()], )?; } @@ -191,7 +192,7 @@ fn verify_trader_index_hint( ) -> ProgramResult { require!( hinted_index % (MARKET_BLOCK_SIZE as DataIndex) == 0, - ManifestError::WrongIndexHintParams, + crate::program::ManifestError::WrongIndexHintParams, "Invalid trader hint index {} did not align", hinted_index, )?; @@ -199,7 +200,7 @@ fn verify_trader_index_hint( get_helper::>(&dynamic_account.dynamic, hinted_index) .get_payload_type() == MarketDataTreeNodeType::ClaimedSeat as u8, - ManifestError::WrongIndexHintParams, + crate::program::ManifestError::WrongIndexHintParams, "Invalid trader hint index {} is not a ClaimedSeat", hinted_index, )?; @@ -207,13 +208,15 @@ fn verify_trader_index_hint( payer .key .eq(dynamic_account.get_trader_key_by_index(hinted_index)), - ManifestError::WrongIndexHintParams, + crate::program::ManifestError::WrongIndexHintParams, "Invalid trader hint index {} did not match payer", hinted_index )?; Ok(()) } +// TODO: Same for invoke_signed + pub fn invoke(ix: &Instruction, account_infos: &[AccountInfo<'_>]) -> ProgramResult { #[cfg(target_os = "solana")] { diff --git a/programs/manifest/src/program/processor/swap.rs b/programs/manifest/src/program/processor/swap.rs index 78522adc0..d09307ddd 100644 --- a/programs/manifest/src/program/processor/swap.rs +++ b/programs/manifest/src/program/processor/swap.rs @@ -2,8 +2,6 @@ use std::cell::RefMut; use crate::{ logs::{emit_stack, PlaceOrderLog}, - market_vault_seeds_with_bump, - program::{invoke, ManifestError}, quantities::{BaseAtoms, QuoteAtoms, QuoteAtomsPerBaseAtom, WrapperU64}, require, state::{ @@ -12,23 +10,36 @@ use crate::{ }, validation::loaders::SwapContext, }; +#[cfg(not(feature = "certora"))] +use crate::{ + market_vault_seeds_with_bump, + program::{invoke, ManifestError}, +}; use borsh::{BorshDeserialize, BorshSerialize}; use hypertree::{trace, DataIndex, NIL}; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program::invoke_signed, pubkey::Pubkey, -}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; use super::shared::get_mut_dynamic_account; +#[cfg(feature = "certora")] +use { + crate::certora::summaries::place_order::place_fully_match_order_with_same_base_and_quote, + early_panic::early_panic, + solana_cvt::token::{spl_token_2022_transfer, spl_token_transfer}, +}; + +use crate::validation::{MintAccountInfo, Signer, TokenAccountInfo, TokenProgram}; +use solana_program::program_error::ProgramError; + #[derive(BorshDeserialize, BorshSerialize)] pub struct SwapParams { - in_atoms: u64, - out_atoms: u64, - is_base_in: bool, + pub in_atoms: u64, + pub out_atoms: u64, + pub is_base_in: bool, // Exact in is a technical term that doesnt actually mean exact. It is // desired. If not that much can be fulfilled, less will be allowed assuming // the min_out/max_in is satisfied. - is_exact_in: bool, + pub is_exact_in: bool, } impl SwapParams { @@ -43,9 +54,19 @@ impl SwapParams { } pub(crate) fn process_swap( - _program_id: &Pubkey, + program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8], +) -> ProgramResult { + let params = SwapParams::try_from_slice(data)?; + process_swap_core(program_id, accounts, params) +} + +#[cfg_attr(all(feature = "certora", not(feature = "certora-test")), early_panic)] +pub(crate) fn process_swap_core( + _program_id: &Pubkey, + accounts: &[AccountInfo], + params: SwapParams, ) -> ProgramResult { let swap_context: SwapContext = SwapContext::load(accounts)?; @@ -80,7 +101,7 @@ pub(crate) fn process_swap( out_atoms, is_base_in, is_exact_in, - } = SwapParams::try_from_slice(data)?; + } = params; trace!("swap in_atoms:{in_atoms} out_atoms:{out_atoms} is_base_in:{is_base_in} is_exact_in:{is_exact_in}"); @@ -121,10 +142,10 @@ pub(crate) fn process_swap( // 4. Exact out base. Use the number of out atoms as the number of atoms to place_order against. let base_atoms: BaseAtoms = if is_exact_in { if is_base_in { - // input=desired max(base) output=checked min(quote) + // input=desired(base) output=min(quote) BaseAtoms::new(in_atoms) } else { - // input=desired max(quote) output=checked min(base) + // input=desired(quote)* output=min(base) // round down base amount to not cross quote limit dynamic_account.impact_base_atoms( true, @@ -134,7 +155,7 @@ pub(crate) fn process_swap( } } else { if is_base_in { - // input=checked max(base) output=desired min(quote) + // input=max(base) output=desired(quote) // round up base amount to ensure not staying below quote limit dynamic_account.impact_base_atoms( false, @@ -142,7 +163,7 @@ pub(crate) fn process_swap( &global_trade_accounts_opts, )? } else { - // input=checked max(quote) output=desired min(base) + // input=max(quote) output=desired(base) BaseAtoms::new(out_atoms) } }; @@ -170,23 +191,27 @@ pub(crate) fn process_swap( let order_type: OrderType = OrderType::ImmediateOrCancel; trace!("swap in:{in_atoms} out:{out_atoms} base/quote:{is_base_in} in/out:{is_exact_in} base:{base_atoms} price:{price}",); + let AddOrderToMarketResult { base_atoms_traded, quote_atoms_traded, order_sequence_number, order_index, .. - } = dynamic_account.place_order(AddOrderToMarketArgs { - market: *market.key, - trader_index, - num_base_atoms: base_atoms, - price, - is_bid: !is_base_in, - last_valid_slot, - order_type, - global_trade_accounts_opts: &global_trade_accounts_opts, - current_slot: None, - })?; + } = place_order( + &mut dynamic_account, + AddOrderToMarketArgs { + market: *market.key, + trader_index, + num_base_atoms: base_atoms, + price, + is_bid: !is_base_in, + last_valid_slot, + order_type, + global_trade_accounts_opts: &global_trade_accounts_opts, + current_slot: None, + }, + )?; if is_exact_in { let out_atoms_traded: u64 = if is_base_in { @@ -217,6 +242,7 @@ pub(crate) fn process_swap( } let (end_base_atoms, end_quote_atoms) = dynamic_account.get_trader_balance(payer.key); + let extra_base_atoms: BaseAtoms = end_base_atoms.checked_sub(initial_base_atoms)?; let extra_quote_atoms: QuoteAtoms = end_quote_atoms.checked_sub(initial_quote_atoms)?; @@ -231,90 +257,49 @@ pub(crate) fn process_swap( let initial_credit_base_atoms: BaseAtoms = BaseAtoms::new(in_atoms); if *token_program_base.key == spl_token_2022::id() { - invoke( - &spl_token_2022::instruction::transfer_checked( - token_program_base.key, - trader_base_account.key, - dynamic_account.fixed.get_base_mint(), - base_vault.key, - payer.key, - &[], - (initial_credit_base_atoms.checked_sub(extra_base_atoms)?).as_u64(), - dynamic_account.fixed.get_base_mint_decimals(), - )?, - &[ - token_program_base.as_ref().clone(), - trader_base_account.as_ref().clone(), - base_vault.as_ref().clone(), - base_mint.unwrap().as_ref().clone(), - payer.as_ref().clone(), - ], + spl_token_2022_transfer_from_trader_to_vault( + &token_program_base, + &trader_base_account, + base_mint, + dynamic_account.fixed.get_base_mint(), + &base_vault, + &payer, + (initial_credit_base_atoms.checked_sub(extra_base_atoms)?).as_u64(), + dynamic_account.fixed.get_base_mint_decimals(), )?; } else { - invoke( - &spl_token::instruction::transfer( - token_program_base.key, - trader_base_account.key, - base_vault.key, - payer.key, - &[], - (initial_credit_base_atoms.checked_sub(extra_base_atoms)?).as_u64(), - )?, - &[ - token_program_base.as_ref().clone(), - trader_base_account.as_ref().clone(), - base_vault.as_ref().clone(), - payer.as_ref().clone(), - ], + spl_token_transfer_from_trader_to_vault( + &token_program_base, + &trader_base_account, + &base_vault, + &payer, + (initial_credit_base_atoms.checked_sub(extra_base_atoms)?).as_u64(), )?; } // Give all but what started there. let quote_vault_bump: u8 = dynamic_account.fixed.get_quote_vault_bump(); if *token_program_quote.key == spl_token_2022::id() { - invoke_signed( - &spl_token_2022::instruction::transfer_checked( - token_program_quote.key, - quote_vault.key, - dynamic_account.fixed.get_quote_mint(), - trader_quote_account.key, - quote_vault.key, - &[], - extra_quote_atoms.as_u64(), - dynamic_account.fixed.get_quote_mint_decimals(), - )?, - &[ - token_program_quote.as_ref().clone(), - quote_vault.as_ref().clone(), - quote_mint.unwrap().as_ref().clone(), - trader_quote_account.as_ref().clone(), - ], - market_vault_seeds_with_bump!( - market.key, - dynamic_account.get_quote_mint(), - quote_vault_bump - ), + spl_token_2022_transfer_from_vault_to_trader( + &token_program_quote, + quote_mint, + dynamic_account.fixed.get_quote_mint(), + "e_vault, + &trader_quote_account, + extra_quote_atoms.as_u64(), + dynamic_account.fixed.get_quote_mint_decimals(), + market.key, + quote_vault_bump, )?; } else { - invoke_signed( - &spl_token::instruction::transfer( - token_program_quote.key, - quote_vault.key, - trader_quote_account.key, - quote_vault.key, - &[], - extra_quote_atoms.as_u64(), - )?, - &[ - token_program_quote.as_ref().clone(), - quote_vault.as_ref().clone(), - trader_quote_account.as_ref().clone(), - ], - market_vault_seeds_with_bump!( - market.key, - dynamic_account.get_quote_mint(), - quote_vault_bump - ), + spl_token_transfer_from_vault_to_trader( + &token_program_quote, + "e_vault, + &trader_quote_account, + extra_quote_atoms.as_u64(), + market.key, + quote_vault_bump, + dynamic_account.fixed.get_quote_mint(), )?; } } else { @@ -326,90 +311,49 @@ pub(crate) fn process_swap( // unused amount. let initial_credit_quote_atoms: QuoteAtoms = QuoteAtoms::new(in_atoms); if *token_program_quote.key == spl_token_2022::id() { - invoke( - &spl_token_2022::instruction::transfer_checked( - token_program_quote.key, - trader_quote_account.key, - dynamic_account.fixed.get_quote_mint(), - quote_vault.key, - payer.key, - &[], - (initial_credit_quote_atoms.checked_sub(extra_quote_atoms)?).as_u64(), - dynamic_account.fixed.get_quote_mint_decimals(), - )?, - &[ - token_program_quote.as_ref().clone(), - trader_quote_account.as_ref().clone(), - quote_mint.unwrap().as_ref().clone(), - quote_vault.as_ref().clone(), - payer.as_ref().clone(), - ], + spl_token_2022_transfer_from_trader_to_vault( + &token_program_quote, + &trader_quote_account, + quote_mint, + dynamic_account.fixed.get_quote_mint(), + "e_vault, + &payer, + (initial_credit_quote_atoms.checked_sub(extra_quote_atoms)?).as_u64(), + dynamic_account.fixed.get_quote_mint_decimals(), )?; } else { - invoke( - &spl_token::instruction::transfer( - token_program_quote.key, - trader_quote_account.key, - quote_vault.key, - payer.key, - &[], - (initial_credit_quote_atoms.checked_sub(extra_quote_atoms)?).as_u64(), - )?, - &[ - token_program_quote.as_ref().clone(), - trader_quote_account.as_ref().clone(), - quote_vault.as_ref().clone(), - payer.as_ref().clone(), - ], + spl_token_transfer_from_trader_to_vault( + &token_program_quote, + &trader_quote_account, + "e_vault, + &payer, + (initial_credit_quote_atoms.checked_sub(extra_quote_atoms)?).as_u64(), )?; } // Give all but what started there. let base_vault_bump: u8 = dynamic_account.fixed.get_base_vault_bump(); if *token_program_base.key == spl_token_2022::id() { - invoke_signed( - &spl_token_2022::instruction::transfer_checked( - token_program_base.key, - base_vault.key, - dynamic_account.get_base_mint(), - trader_base_account.key, - base_vault.key, - &[], - extra_base_atoms.as_u64(), - dynamic_account.fixed.get_base_mint_decimals(), - )?, - &[ - token_program_base.as_ref().clone(), - base_vault.as_ref().clone(), - base_mint.unwrap().as_ref().clone(), - trader_base_account.as_ref().clone(), - ], - market_vault_seeds_with_bump!( - market.key, - dynamic_account.get_base_mint(), - base_vault_bump - ), + spl_token_2022_transfer_from_vault_to_trader( + &token_program_base, + base_mint, + dynamic_account.get_base_mint(), + &base_vault, + &trader_base_account, + extra_base_atoms.as_u64(), + dynamic_account.fixed.get_base_mint_decimals(), + market.key, + base_vault_bump, )?; } else { - invoke_signed( - &spl_token::instruction::transfer( - token_program_base.key, - base_vault.key, - trader_base_account.key, - base_vault.key, - &[], - extra_base_atoms.as_u64(), - )?, - &[ - token_program_base.as_ref().clone(), - base_vault.as_ref().clone(), - trader_base_account.as_ref().clone(), - ], - market_vault_seeds_with_bump!( - market.key, - dynamic_account.get_base_mint(), - base_vault_bump - ), + spl_token_transfer_from_vault_to_trader( + &token_program_base, + &base_vault, + &trader_base_account, + extra_base_atoms.as_u64(), + market.key, + base_vault_bump, + dynamic_account.get_base_mint(), )?; } } @@ -439,3 +383,198 @@ pub(crate) fn process_swap( Ok(()) } + +#[cfg(not(feature = "certora"))] +fn place_order( + dynamic_account: &mut MarketRefMut, + args: AddOrderToMarketArgs, +) -> Result { + dynamic_account.place_order(args) +} + +#[cfg(feature = "certora")] +fn place_order( + market: &mut MarketRefMut, + args: AddOrderToMarketArgs, +) -> Result { + place_fully_match_order_with_same_base_and_quote(market, args) +} + +/** Transfer from base (quote) trader to base (quote) vault using SPL Token **/ +#[cfg(not(feature = "certora"))] +fn spl_token_transfer_from_trader_to_vault<'a, 'info>( + token_program: &TokenProgram<'a, 'info>, + trader_account: &TokenAccountInfo<'a, 'info>, + vault: &TokenAccountInfo<'a, 'info>, + payer: &Signer<'a, 'info>, + amount: u64, +) -> ProgramResult { + invoke( + &spl_token::instruction::transfer( + token_program.key, + trader_account.key, + vault.key, + payer.key, + &[], + amount, + )?, + &[ + token_program.as_ref().clone(), + trader_account.as_ref().clone(), + vault.as_ref().clone(), + payer.as_ref().clone(), + ], + ) +} +#[cfg(feature = "certora")] +/** (Summary) Transfer from base (quote) trader to base (quote) vault using SPL Token **/ +fn spl_token_transfer_from_trader_to_vault<'a, 'info>( + _token_program: &TokenProgram<'a, 'info>, + trader_account: &TokenAccountInfo<'a, 'info>, + vault: &TokenAccountInfo<'a, 'info>, + payer: &Signer<'a, 'info>, + amount: u64, +) -> ProgramResult { + spl_token_transfer(trader_account.info, vault.info, payer.info, amount) +} + +/** Transfer from base (quote) trader to base (quote) vault using SPL Token 2022 **/ +#[cfg(not(feature = "certora"))] +fn spl_token_2022_transfer_from_trader_to_vault<'a, 'info>( + token_program: &TokenProgram<'a, 'info>, + trader_account: &TokenAccountInfo<'a, 'info>, + mint: Option>, + mint_pubkey: &Pubkey, + vault: &TokenAccountInfo<'a, 'info>, + payer: &Signer<'a, 'info>, + amount: u64, + decimals: u8, +) -> ProgramResult { + invoke( + &spl_token_2022::instruction::transfer_checked( + token_program.key, + trader_account.key, + mint_pubkey, + vault.key, + payer.key, + &[], + amount, + decimals, + )?, + &[ + token_program.as_ref().clone(), + trader_account.as_ref().clone(), + vault.as_ref().clone(), + mint.unwrap().as_ref().clone(), + payer.as_ref().clone(), + ], + ) +} + +#[cfg(feature = "certora")] +/** (Summary) Transfer from base (quote) trader to base (quote) vault using SPL Token 2022 **/ +fn spl_token_2022_transfer_from_trader_to_vault<'a, 'info>( + _token_program: &TokenProgram<'a, 'info>, + trader_account: &TokenAccountInfo<'a, 'info>, + _mint: Option>, + _mint_pubkey: &Pubkey, + vault: &TokenAccountInfo<'a, 'info>, + payer: &Signer<'a, 'info>, + amount: u64, + _decimals: u8, +) -> ProgramResult { + spl_token_2022_transfer(trader_account.info, vault.info, payer.info, amount) +} + +/** Transfer from base (quote) vault to base (quote) trader using SPL Token **/ +#[cfg(not(feature = "certora"))] +fn spl_token_transfer_from_vault_to_trader<'a, 'info>( + token_program: &TokenProgram<'a, 'info>, + vault: &TokenAccountInfo<'a, 'info>, + trader_account: &TokenAccountInfo<'a, 'info>, + amount: u64, + market_key: &Pubkey, + vault_bump: u8, + mint_pubkey: &Pubkey, +) -> ProgramResult { + solana_program::program::invoke_signed( + &spl_token::instruction::transfer( + token_program.key, + vault.key, + trader_account.key, + vault.key, + &[], + amount, + )?, + &[ + token_program.as_ref().clone(), + vault.as_ref().clone(), + trader_account.as_ref().clone(), + ], + market_vault_seeds_with_bump!(market_key, mint_pubkey, vault_bump), + ) +} + +#[cfg(feature = "certora")] +/** (Summary) Transfer from base (quote) vault to base (quote) trader using SPL Token **/ +fn spl_token_transfer_from_vault_to_trader<'a, 'info>( + _token_program: &TokenProgram<'a, 'info>, + vault: &TokenAccountInfo<'a, 'info>, + trader_account: &TokenAccountInfo<'a, 'info>, + amount: u64, + _market_key: &Pubkey, + _vault_bump: u8, + _mint_pubkey: &Pubkey, +) -> ProgramResult { + spl_token_transfer(vault.info, trader_account.info, vault.info, amount) +} + +/** Transfer from base (quote) vault to base (quote) trader using SPL Token 2022 **/ +#[cfg(not(feature = "certora"))] +fn spl_token_2022_transfer_from_vault_to_trader<'a, 'info>( + token_program: &TokenProgram<'a, 'info>, + mint: Option>, + mint_pubkey: &Pubkey, + vault: &TokenAccountInfo<'a, 'info>, + trader_account: &TokenAccountInfo<'a, 'info>, + amount: u64, + decimals: u8, + market_key: &Pubkey, + vault_bump: u8, +) -> ProgramResult { + solana_program::program::invoke_signed( + &spl_token_2022::instruction::transfer_checked( + token_program.key, + vault.key, + mint_pubkey, + trader_account.key, + vault.key, + &[], + amount, + decimals, + )?, + &[ + token_program.as_ref().clone(), + vault.as_ref().clone(), + mint.unwrap().as_ref().clone(), + trader_account.as_ref().clone(), + ], + market_vault_seeds_with_bump!(market_key, mint_pubkey, vault_bump), + ) +} + +#[cfg(feature = "certora")] +/** (Summary) Transfer from base (quote) vault to base (quote) trader using SPL Token 2022 **/ +fn spl_token_2022_transfer_from_vault_to_trader<'a, 'info>( + _token_program: &TokenProgram<'a, 'info>, + _mint: Option>, + _mint_pubkey: &Pubkey, + vault: &TokenAccountInfo<'a, 'info>, + trader_account: &TokenAccountInfo<'a, 'info>, + amount: u64, + _decimals: u8, + _market_key: &Pubkey, + _vault_bump: u8, +) -> ProgramResult { + spl_token_2022_transfer(vault.info, trader_account.info, vault.info, amount) +} diff --git a/programs/manifest/src/program/processor/withdraw.rs b/programs/manifest/src/program/processor/withdraw.rs index cbf3d3b7e..6e2b74baf 100644 --- a/programs/manifest/src/program/processor/withdraw.rs +++ b/programs/manifest/src/program/processor/withdraw.rs @@ -1,19 +1,24 @@ use std::cell::RefMut; +use super::get_trader_index_with_hint; use crate::{ logs::{emit_stack, WithdrawLog}, - market_vault_seeds_with_bump, program::get_mut_dynamic_account, state::MarketRefMut, - validation::loaders::WithdrawContext, + validation::{loaders::WithdrawContext, MintAccountInfo, TokenAccountInfo, TokenProgram}, }; use borsh::{BorshDeserialize, BorshSerialize}; use hypertree::DataIndex; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program::invoke_signed, pubkey::Pubkey, -}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; -use super::get_trader_index_with_hint; +#[cfg(not(feature = "certora"))] +use {crate::market_vault_seeds_with_bump, solana_program::program::invoke_signed}; + +#[cfg(feature = "certora")] +use { + early_panic::early_panic, + solana_cvt::token::{spl_token_2022_transfer, spl_token_transfer}, +}; #[derive(BorshDeserialize, BorshSerialize)] pub struct WithdrawParams { @@ -31,15 +36,25 @@ impl WithdrawParams { } pub(crate) fn process_withdraw( - _program_id: &Pubkey, + program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8], +) -> ProgramResult { + let params = WithdrawParams::try_from_slice(data)?; + process_withdraw_core(program_id, accounts, params) +} + +#[cfg_attr(all(feature = "certora", not(feature = "certora-test")), early_panic)] +pub(crate) fn process_withdraw_core( + _program_id: &Pubkey, + accounts: &[AccountInfo], + params: WithdrawParams, ) -> ProgramResult { let withdraw_context: WithdrawContext = WithdrawContext::load(accounts)?; let WithdrawParams { amount_atoms, trader_index_hint, - } = WithdrawParams::try_from_slice(data)?; + } = params; let WithdrawContext { market, @@ -70,50 +85,30 @@ pub(crate) fn process_withdraw( }; if *vault.owner == spl_token_2022::id() { - invoke_signed( - &spl_token_2022::instruction::transfer_checked( - token_program.key, - vault.key, - if is_base { - dynamic_account.fixed.get_base_mint() - } else { - dynamic_account.get_quote_mint() - }, - trader_token.key, - vault.key, - &[], - amount_atoms, - if is_base { - dynamic_account.fixed.get_base_mint_decimals() - } else { - dynamic_account.fixed.get_quote_mint_decimals() - }, - )?, - &[ - token_program.as_ref().clone(), - vault.as_ref().clone(), - mint.as_ref().clone(), - trader_token.as_ref().clone(), - payer.as_ref().clone(), - ], - market_vault_seeds_with_bump!(market.key, mint_key, bump), + spl_token_2022_transfer_from_vault_to_trader_fixed( + &token_program, + Some(mint), + mint_key, + &vault, + &trader_token, + amount_atoms, + if is_base { + dynamic_account.fixed.get_base_mint_decimals() + } else { + dynamic_account.fixed.get_quote_mint_decimals() + }, + market.key, + bump, )?; } else { - invoke_signed( - &spl_token::instruction::transfer( - token_program.key, - vault.key, - trader_token.key, - vault.key, - &[], - amount_atoms, - )?, - &[ - token_program.as_ref().clone(), - vault.as_ref().clone(), - trader_token.as_ref().clone(), - ], - market_vault_seeds_with_bump!(market.key, mint_key, bump), + spl_token_transfer_from_vault_to_trader( + &token_program, + &vault, + &trader_token, + amount_atoms, + market.key, + bump, + mint_key, )?; } @@ -134,3 +129,97 @@ pub(crate) fn process_withdraw( Ok(()) } + +/** Transfer from base (quote) vault to base (quote) trader using SPL Token **/ +#[cfg(not(feature = "certora"))] +fn spl_token_transfer_from_vault_to_trader<'a, 'info>( + token_program: &TokenProgram<'a, 'info>, + vault: &TokenAccountInfo<'a, 'info>, + trader_account: &TokenAccountInfo<'a, 'info>, + amount: u64, + market_key: &Pubkey, + vault_bump: u8, + mint_pubkey: &Pubkey, +) -> ProgramResult { + invoke_signed( + &spl_token::instruction::transfer( + token_program.key, + vault.key, + trader_account.key, + vault.key, + &[], + amount, + )?, + &[ + token_program.as_ref().clone(), + vault.as_ref().clone(), + trader_account.as_ref().clone(), + ], + market_vault_seeds_with_bump!(market_key, mint_pubkey, vault_bump), + ) +} + +#[cfg(feature = "certora")] +/** (Summary) Transfer from base (quote) vault to base (quote) trader using SPL Token **/ +fn spl_token_transfer_from_vault_to_trader<'a, 'info>( + _token_program: &TokenProgram<'a, 'info>, + vault: &TokenAccountInfo<'a, 'info>, + trader_account: &TokenAccountInfo<'a, 'info>, + amount: u64, + _market_key: &Pubkey, + _vault_bump: u8, + _mint_pubkey: &Pubkey, +) -> ProgramResult { + spl_token_transfer(vault.info, trader_account.info, vault.info, amount) +} + +/** Transfer from base (quote) vault to base (quote) trader using SPL Token 2022 **/ +#[cfg(not(feature = "certora"))] +fn spl_token_2022_transfer_from_vault_to_trader_fixed<'a, 'info>( + token_program: &TokenProgram<'a, 'info>, + mint: Option>, + mint_key: &Pubkey, + vault: &TokenAccountInfo<'a, 'info>, + trader_token: &TokenAccountInfo<'a, 'info>, + amount_atoms: u64, + decimals: u8, + market_key: &Pubkey, + bump: u8, +) -> ProgramResult { + invoke_signed( + &spl_token_2022::instruction::transfer_checked( + token_program.key, + vault.key, + mint_key, + trader_token.key, + vault.key, + &[], + amount_atoms, + decimals, + )?, + &[ + token_program.as_ref().clone(), + vault.as_ref().clone(), + mint.unwrap().as_ref().clone(), + trader_token.as_ref().clone(), + ], + market_vault_seeds_with_bump!(market_key, mint_key, bump), + ) +} + +// TODO: Share these with swap and deposit. +#[cfg(feature = "certora")] +/** (Summary) Transfer from base (quote) vault to base (quote) trader using SPL Token 2022 **/ +fn spl_token_2022_transfer_from_vault_to_trader_fixed<'a, 'info>( + _token_program: &TokenProgram<'a, 'info>, + _mint: Option>, + _mint_key: &Pubkey, + vault: &TokenAccountInfo<'a, 'info>, + trader_token: &TokenAccountInfo<'a, 'info>, + amount_atoms: u64, + _decimals: u8, + _market_key: &Pubkey, + _bump: u8, +) -> ProgramResult { + spl_token_2022_transfer(vault.info, trader_token.info, vault.info, amount_atoms) +} diff --git a/programs/manifest/src/quantities.rs b/programs/manifest/src/quantities.rs index 057209d50..8b3d5c2b0 100644 --- a/programs/manifest/src/quantities.rs +++ b/programs/manifest/src/quantities.rs @@ -4,7 +4,7 @@ use bytemuck::{Pod, Zeroable}; use hypertree::trace; use shank::ShankAccount; use solana_program::program_error::ProgramError; -use static_assertions::{const_assert, const_assert_eq}; +use static_assertions::const_assert; use std::{ cmp::Ordering, fmt::Display, @@ -234,6 +234,7 @@ pub struct QuoteAtomsPerBaseAtom { // on the u128 type, which is not necessary given that the target architecture // has no native support for u128 math and requires us only to be 8 byte // aligned. +#[cfg(not(feature = "certora"))] const fn u128_to_u64_slice(a: u128) -> [u64; 2] { unsafe { let ptr: *const u128 = &a; @@ -247,10 +248,12 @@ fn u64_slice_to_u128(a: [u64; 2]) -> u128 { } } +#[cfg(not(feature = "certora"))] const ATOM_LIMIT: u128 = u64::MAX as u128; const D18: u128 = 10u128.pow(18); const D18F: f64 = D18 as f64; +#[cfg(not(feature = "certora"))] const DECIMAL_CONSTANTS: [u128; 27] = [ 10u128.pow(26), 10u128.pow(25), @@ -281,16 +284,23 @@ const DECIMAL_CONSTANTS: [u128; 27] = [ 10u128.pow(00), ]; // ensures that the index lookup is correct when converting from floating point -const_assert_eq!( +#[cfg(not(feature = "certora"))] +static_assertions::const_assert_eq!( DECIMAL_CONSTANTS[QuoteAtomsPerBaseAtom::MAX_EXP as usize], D18 ); // ensures that we can remove bounds checks on certain multiplications +#[cfg(not(feature = "certora"))] const_assert!(DECIMAL_CONSTANTS[0] * (u32::MAX as u128) < u128::MAX); + const_assert!(D18 * (u64::MAX as u128) < u128::MAX); -// Prices +#[cfg(feature = "certora")] +#[path = "quantities_certora.rs"] +mod quantities_certora; + +#[cfg(not(feature = "certora"))] impl QuoteAtomsPerBaseAtom { pub const ZERO: Self = QuoteAtomsPerBaseAtom { inner: [0; 2] }; pub const MIN: Self = QuoteAtomsPerBaseAtom::from_mantissa_and_exponent_(1, Self::MIN_EXP); @@ -307,9 +317,9 @@ impl QuoteAtomsPerBaseAtom { -10 -> [18] -> D08 -18 -> [26] -> D0 */ - let offset = (Self::MAX_EXP as i64).wrapping_sub(exponent as i64) as usize; + let offset: usize = (Self::MAX_EXP as i64).wrapping_sub(exponent as i64) as usize; // can not overflow 10^26 * u32::MAX < u128::MAX - let inner = DECIMAL_CONSTANTS[offset].wrapping_mul(mantissa as u128); + let inner: u128 = DECIMAL_CONSTANTS[offset].wrapping_mul(mantissa as u128); QuoteAtomsPerBaseAtom { inner: u128_to_u64_slice(inner), } @@ -508,6 +518,31 @@ impl BaseAtoms { } } +#[cfg(feature = "certora")] +mod nondet { + use super::*; + + impl ::nondet::Nondet for BaseAtoms { + fn nondet() -> Self { + Self::new(::nondet::nondet()) + } + } + + impl ::nondet::Nondet for QuoteAtoms { + fn nondet() -> Self { + Self::new(::nondet::nondet()) + } + } + + impl ::nondet::Nondet for QuoteAtomsPerBaseAtom { + fn nondet() -> Self { + Self { + inner: [::nondet::nondet(), ::nondet::nondet()], + } + } + } +} + #[test] fn test_new_constructor_macro() { let base_atoms_1: BaseAtoms = BaseAtoms::new(5); diff --git a/programs/manifest/src/quantities_certora.rs b/programs/manifest/src/quantities_certora.rs new file mode 100644 index 000000000..4582cbd55 --- /dev/null +++ b/programs/manifest/src/quantities_certora.rs @@ -0,0 +1,106 @@ +#![cfg(feature = "certora")] +use super::*; + +impl QuoteAtomsPerBaseAtom { + pub const ZERO: Self = QuoteAtomsPerBaseAtom { inner: [0; 2] }; + pub const MIN: Self = QuoteAtomsPerBaseAtom { inner: [1, 0] }; + pub const MAX: Self = QuoteAtomsPerBaseAtom { + inner: [u32::MAX as u64, 0], + }; + + pub const MIN_EXP: i8 = -18; + pub const MAX_EXP: i8 = 8; + + // 4 decimal points to fit price into 32 bit + const DECIMALS: u64 = 10u64.pow(4); + + #[inline(always)] + pub fn from_mantissa_and_exponent_(_mantissa: u32, _exponent: i8) -> Self { + cvt::cvt_assert!(false); + unreachable!() + } + + pub fn try_from_mantissa_and_exponent( + mantissa: u32, + exponent: i8, + ) -> Result { + if exponent > Self::MAX_EXP { + trace!("invalid exponent {exponent} > 8 would truncate",); + return Err(PriceConversionError(0x1)); + } + if exponent < Self::MIN_EXP { + trace!("invalid exponent {exponent} < -18 would truncate",); + return Err(PriceConversionError(0x2)); + } + Ok(Self::from_mantissa_and_exponent_(mantissa, exponent)) + } + + #[inline(always)] + pub fn checked_base_for_quote( + self, + quote_atoms: QuoteAtoms, + round_up: bool, + ) -> Result { + // prevents division by zero further down the line. zero is not an + // ideal answer, but this is only used in impact_base_atoms, which + // is used to calculate error free order sizes and for that purpose + // it works well. + if self == Self::ZERO { + return Ok(BaseAtoms::ZERO); + } + let dividend = Self::DECIMALS.wrapping_mul(quote_atoms.inner); + let inner = self.inner[0]; + + let base_atoms = if round_up { + dividend.div_ceil(inner) + } else { + dividend.div(inner) + }; + + Ok(BaseAtoms::new(base_atoms)) + } + + #[inline(always)] + fn checked_quote_for_base_( + self, + base_atoms: BaseAtoms, + round_up: bool, + ) -> Result { + let inner = self.inner[0]; + let product = inner + .checked_mul(base_atoms.inner) + .ok_or(PriceConversionError(0x8))?; + let quote_atoms = if round_up { + product.div_ceil(Self::DECIMALS) + } else { + product.div(Self::DECIMALS) + }; + Ok(quote_atoms) + } + + #[inline(always)] + pub fn checked_quote_for_base( + self, + other: BaseAtoms, + round_up: bool, + ) -> Result { + self.checked_quote_for_base_(other, round_up) + .map(|r| QuoteAtoms::new(r)) + } + + #[inline(always)] + pub fn checked_effective_price( + self, + _num_base_atoms: BaseAtoms, + _is_bid: bool, + ) -> Result { + cvt::cvt_assert!(false); + unreachable!(); + } + + pub fn nondet_price_u32() -> Self { + let x = ::nondet::nondet(); + cvt::cvt_assume!(x <= u32::MAX as u64); + Self { inner: [x, 0] } + } +} diff --git a/programs/manifest/src/state/claimed_seat.rs b/programs/manifest/src/state/claimed_seat.rs index 12f16f15f..77b7cfb61 100644 --- a/programs/manifest/src/state/claimed_seat.rs +++ b/programs/manifest/src/state/claimed_seat.rs @@ -1,5 +1,7 @@ use std::mem::size_of; +#[cfg(feature = "certora")] +use crate::quantities::WrapperU64; use crate::quantities::{BaseAtoms, QuoteAtoms}; use bytemuck::{Pod, Zeroable}; use shank::ShankType; @@ -43,6 +45,19 @@ impl ClaimedSeat { } } +#[cfg(feature = "certora")] +impl nondet::Nondet for ClaimedSeat { + fn nondet() -> Self { + ClaimedSeat { + trader: nondet::nondet(), + base_withdrawable_balance: BaseAtoms::new(nondet::nondet()), + quote_withdrawable_balance: QuoteAtoms::new(nondet::nondet()), + quote_volume: QuoteAtoms::new(nondet::nondet()), + _padding: [0; 8], + } + } +} + impl Ord for ClaimedSeat { fn cmp(&self, other: &Self) -> Ordering { (self.trader).cmp(&(other.trader)) diff --git a/programs/manifest/src/state/cvt_db_mock.rs b/programs/manifest/src/state/cvt_db_mock.rs new file mode 100644 index 000000000..95e3bfbb7 --- /dev/null +++ b/programs/manifest/src/state/cvt_db_mock.rs @@ -0,0 +1,501 @@ +use std::marker::PhantomData; + +use crate::state::{market::ClaimedSeat, RestingOrder, MARKET_BLOCK_SIZE}; +use hypertree::{get_helper, get_mut_helper, DataIndex, RBNode, NIL}; +use solana_program::pubkey::Pubkey; + +use crate::certora::utils::alloc_havoced; +use cvt::{cvt_assert, cvt_assume}; +use nondet::nondet; + +const NUM_BLOCKS: usize = 10; +// -- must be big enough so that all INDEX global variables are a legal index +// -- into an array of that size +const GLOBAL_DATA_LEN: usize = NUM_BLOCKS * MARKET_BLOCK_SIZE; + +const MAIN_SEAT_INDEX: u64 = 0; +const SECOND_SEAT_INDEX: u64 = MARKET_BLOCK_SIZE as u64; + +static mut MAIN_SEAT_PK: *mut Pubkey = std::ptr::null_mut(); +static mut IS_MAIN_SEAT_TAKEN: u64 = 0; + +static mut SECOND_SEAT_PK: *mut Pubkey = std::ptr::null_mut(); +static mut IS_SECOND_SEAT_TAKEN: u64 = 0; + +static mut SEAT_DATA: *mut [u8; GLOBAL_DATA_LEN] = std::ptr::null_mut(); + +const MAIN_BID_ORDER_INDEX: u64 = 0; +static mut MAIN_BID_ORDER_TAKEN: u64 = 0; +static mut BID_ORDER_DATA: *mut [u8; GLOBAL_DATA_LEN] = std::ptr::null_mut(); + +const MAIN_ASK_ORDER_INDEX: u64 = 2 * MARKET_BLOCK_SIZE as u64; +static mut MAIN_ASK_ORDER_TAKEN: u64 = 0; +static mut ASK_ORDER_DATA: *mut [u8; GLOBAL_DATA_LEN] = std::ptr::null_mut(); + +const MAIN_SEAT_DATA_IDX: DataIndex = 0u32; +const SECOND_SEAT_DATA_IDX: DataIndex = MARKET_BLOCK_SIZE as u32; +const MAIN_BID_ORDER_DATA_IDX: DataIndex = 0u32; +const MAIN_ASK_ORDER_DATA_IDX: DataIndex = 0u32; + +pub fn init_mock() { + init_mock_traders(); + init_mock_orders(); +} + +fn init_mock_traders() { + unsafe { + // MAIN_SEAT_INDEX = 0; + MAIN_SEAT_PK = alloc_havoced::(); + IS_MAIN_SEAT_TAKEN = nondet(); + + // SECOND_SEAT_INDEX = MARKET_BLOCK_SIZE as u64; + SECOND_SEAT_PK = alloc_havoced::(); + IS_SECOND_SEAT_TAKEN = nondet(); + + SEAT_DATA = alloc_havoced::<[u8; GLOBAL_DATA_LEN]>() + } +} + +pub fn cvt_assume_main_trader_has_seat(pk: &Pubkey) { + cvt_assume!(pk == main_trader_pk()); + cvt_assume!(unsafe { IS_MAIN_SEAT_TAKEN } == 1); +} + +pub fn cvt_assume_second_trader_has_seat(pk: &Pubkey) { + cvt_assume!(pk == second_trader_pk()); + cvt_assume!(unsafe { IS_SECOND_SEAT_TAKEN } == 1); +} + +pub fn main_trader_pk() -> &'static Pubkey { + unsafe { &*MAIN_SEAT_PK } +} + +pub fn main_trader_index() -> DataIndex { + MAIN_SEAT_INDEX as DataIndex +} + +pub fn second_trader_pk() -> &'static Pubkey { + unsafe { &*SECOND_SEAT_PK } +} + +pub fn second_trader_index() -> DataIndex { + SECOND_SEAT_INDEX as DataIndex +} + +/// Read a `RBNode` in an array of data at a given index. +pub fn get_helper_seat(_data: &[u8], index: DataIndex) -> &'static RBNode { + if index == main_trader_index() { + get_helper::>(unsafe { &*SEAT_DATA }, MAIN_SEAT_DATA_IDX) + } else if index == second_trader_index() { + get_helper::>(unsafe { &*SEAT_DATA }, SECOND_SEAT_DATA_IDX) + } else { + cvt_assert!(false); + // -- return something to make Rust happy. Protected by assert above + get_helper::>(unsafe { &*SEAT_DATA }, index) + } +} + +/// Read a `RBNode` in an array of data at a given index. +pub fn get_mut_helper_seat(_data: &mut [u8], index: DataIndex) -> &mut RBNode { + if index == main_trader_index() { + get_mut_helper::>(unsafe { &mut *SEAT_DATA }, MAIN_SEAT_DATA_IDX) + } else if index == second_trader_index() { + get_mut_helper::>(unsafe { &mut *SEAT_DATA }, SECOND_SEAT_DATA_IDX) + } else { + cvt_assert!(false); + // -- return something to make Rust happy. Protected by assert above + get_mut_helper::>(unsafe { &mut *SEAT_DATA }, index) + } +} + +pub fn is_main_seat_taken() -> bool { + !is_main_seat_free() +} +pub fn is_main_seat_free() -> bool { + unsafe { IS_MAIN_SEAT_TAKEN == 0 } +} + +pub fn take_main_seat() { + cvt_assert!(is_main_seat_free()); + unsafe { IS_MAIN_SEAT_TAKEN = 1 }; +} + +pub fn release_main_seat() { + cvt_assert!(is_main_seat_taken()); + unsafe { IS_MAIN_SEAT_TAKEN = 0 }; +} + +pub fn is_second_seat_taken() -> bool { + !is_second_seat_free() +} +pub fn is_second_seat_free() -> bool { + unsafe { IS_SECOND_SEAT_TAKEN == 0 } +} + +pub fn take_second_seat() { + cvt_assert!(is_second_seat_free()); + unsafe { IS_SECOND_SEAT_TAKEN = 1 }; +} + +pub fn release_second_seat() { + cvt_assert!(is_second_seat_taken()); + unsafe { IS_SECOND_SEAT_TAKEN = 1 }; +} + +pub fn main_bid_order_index() -> DataIndex { + MAIN_BID_ORDER_INDEX as DataIndex +} + +pub fn main_ask_order_index() -> DataIndex { + MAIN_ASK_ORDER_INDEX as DataIndex +} + +fn init_mock_orders() { + unsafe { + BID_ORDER_DATA = alloc_havoced::<[u8; GLOBAL_DATA_LEN]>(); + MAIN_BID_ORDER_TAKEN = nondet(); + + ASK_ORDER_DATA = alloc_havoced::<[u8; GLOBAL_DATA_LEN]>(); + MAIN_ASK_ORDER_TAKEN = nondet(); + } +} + +pub fn get_helper_order(data: &[u8], index: DataIndex) -> &RBNode { + if index == main_ask_order_index() { + get_helper_ask_order(data, index) + } else if index == main_bid_order_index() { + get_helper_bid_order(data, index) + } else { + cvt_assert!(false); + get_helper::>(data, index) + } +} +pub fn get_mut_helper_order(data: &mut [u8], index: DataIndex) -> &mut RBNode { + if index == main_ask_order_index() { + get_mut_helper_ask_order(data, index) + } else if index == main_bid_order_index() { + get_mut_helper_bid_order(data, index) + } else { + cvt_assert!(false); + get_mut_helper::>(data, index) + } +} + +pub fn get_helper_bid_order(_data: &[u8], index: DataIndex) -> &RBNode { + if index == main_bid_order_index() { + get_helper::>(unsafe { &*BID_ORDER_DATA }, MAIN_BID_ORDER_DATA_IDX) + } else { + cvt_assert!(false); + get_helper::>(unsafe { &*BID_ORDER_DATA }, index) + } +} +pub fn get_mut_helper_bid_order(_data: &mut [u8], index: DataIndex) -> &mut RBNode { + if index == main_bid_order_index() { + get_mut_helper::>( + unsafe { &mut *BID_ORDER_DATA }, + MAIN_BID_ORDER_DATA_IDX, + ) + } else { + cvt_assert!(false); + get_mut_helper::>(unsafe { &mut *BID_ORDER_DATA }, index) + } +} + +pub fn get_helper_ask_order(_data: &[u8], index: DataIndex) -> &RBNode { + if index == main_ask_order_index() { + get_helper::>(unsafe { &*ASK_ORDER_DATA }, MAIN_ASK_ORDER_DATA_IDX) + } else { + cvt_assert!(false); + get_helper::>(unsafe { &*ASK_ORDER_DATA }, index) + } +} +pub fn get_mut_helper_ask_order(_data: &mut [u8], index: DataIndex) -> &mut RBNode { + if index == main_ask_order_index() { + get_mut_helper::>( + unsafe { &mut *ASK_ORDER_DATA }, + MAIN_ASK_ORDER_DATA_IDX, + ) + } else { + cvt_assert!(false); + get_mut_helper::>(unsafe { &mut *ASK_ORDER_DATA }, index) + } +} + +pub fn is_bid_order_taken() -> bool { + !is_bid_order_free() +} +pub fn is_bid_order_free() -> bool { + unsafe { MAIN_BID_ORDER_TAKEN == 0 } +} +pub fn take_bid_order() { + unsafe { + cvt_assert!(is_bid_order_free()); + MAIN_BID_ORDER_TAKEN = 1 + }; +} + +pub fn release_bid_order() { + unsafe { + cvt_assert!(is_bid_order_taken()); + MAIN_BID_ORDER_TAKEN = 0; + } +} + +pub fn is_ask_order_taken() -> bool { + !is_ask_order_free() +} +pub fn is_ask_order_free() -> bool { + unsafe { MAIN_ASK_ORDER_TAKEN == 0 } +} + +pub fn take_ask_order() { + unsafe { + cvt_assert!(is_ask_order_free()); + MAIN_ASK_ORDER_TAKEN = 1 + }; +} + +pub fn release_ask_order() { + unsafe { + cvt_assert!(is_ask_order_taken()); + MAIN_ASK_ORDER_TAKEN = 0; + } +} + +pub struct CvtClaimedSeatTreeReadOnly<'a> { + _root_index: DataIndex, + _max_index: DataIndex, + phantom: std::marker::PhantomData<&'a [u8]>, +} + +impl<'a> CvtClaimedSeatTreeReadOnly<'a> { + pub fn new(_data: &'a [u8], _root_index: DataIndex, _max_index: DataIndex) -> Self { + Self { + _root_index, + _max_index, + phantom: PhantomData, + } + } + + pub fn lookup_index(&self, seat: &ClaimedSeat) -> DataIndex { + if &seat.trader == main_trader_pk() { + if is_main_seat_taken() { + main_trader_index() + } else { + NIL + } + } else if &seat.trader == second_trader_pk() { + if is_second_seat_taken() { + second_trader_index() + } else { + NIL + } + } else { + cvt_assert!(false); + NIL + } + } +} + +pub struct CvtClaimedSeatTree<'a> { + root_index: DataIndex, + _max_index: DataIndex, + phantom: std::marker::PhantomData<&'a mut [u8]>, +} + +impl<'a> CvtClaimedSeatTree<'a> { + pub fn new(_data: &'a mut [u8], root_index: DataIndex, _max_index: DataIndex) -> Self { + Self { + root_index, + _max_index, + phantom: PhantomData, + } + } + + pub fn get_root_index(&self) -> DataIndex { + self.root_index + } + + pub fn lookup_index(&self, seat: &ClaimedSeat) -> DataIndex { + if &seat.trader == main_trader_pk() { + if is_main_seat_taken() { + main_trader_index() + } else { + NIL + } + } else if &seat.trader == second_trader_pk() { + if is_second_seat_taken() { + second_trader_index() + } else { + NIL + } + } else { + cvt_assert!(false); + NIL + } + } + + pub fn remove_by_index(&mut self, index: DataIndex) { + if index == main_trader_index() { + release_main_seat(); + } else if index == second_trader_index() { + release_second_seat(); + } else { + cvt_assert!(false); + } + } + + pub fn insert(&mut self, index: DataIndex, seat: ClaimedSeat) { + if index == main_trader_index() { + let mut dynamic = [0u8; 8]; + let seat_node: &mut RBNode = + get_mut_helper_seat(&mut dynamic, main_trader_index()); + let new_seat_node: RBNode = RBNode { + left: NIL, + right: NIL, + parent: NIL, + color: hypertree::Color::Red, + value: seat, + payload_type: 0, + _unused_padding: 0, + }; + take_main_seat(); + *seat_node = new_seat_node; + } else if index == second_trader_index() { + let mut dynamic = [0u8; 8]; + let seat_node: &mut RBNode = + get_mut_helper_seat(&mut dynamic, second_trader_index()); + let new_seat_node: RBNode = RBNode { + left: NIL, + right: NIL, + parent: NIL, + color: hypertree::Color::Red, + value: seat, + payload_type: 0, + _unused_padding: 0, + }; + take_second_seat(); + *seat_node = new_seat_node; + } else { + cvt_assert!(false); + } + } +} + +pub struct CvtBooksideReadOnlyIterator { + pub calls: u64, +} + +impl std::iter::Iterator for CvtBooksideReadOnlyIterator { + type Item = (DataIndex, RestingOrder); + + fn next(&mut self) -> Option { + // TODO: this does not support when both main orders are present. + if is_bid_order_taken() && self.calls < 1 { + let order_index = main_bid_order_index(); + let dynamic = &mut [0; 8]; + let resting_order: &RestingOrder = get_helper_order(dynamic, order_index).get_value(); + self.calls += 1; + Some((order_index, *resting_order)) + } else if is_ask_order_taken() && self.calls < 1 { + let order_index = main_ask_order_index(); + let dynamic = &mut [0; 8]; + let resting_order: &RestingOrder = get_helper_order(dynamic, order_index).get_value(); + self.calls += 1; + Some((order_index, *resting_order)) + } else { + None + } + } +} + +pub struct CvtBooksideReadOnly<'a> { + root_index: DataIndex, + max_index: DataIndex, + phantom: std::marker::PhantomData<&'a [u8]>, +} + +impl<'a> CvtBooksideReadOnly<'a> { + pub fn new(_data: &'a [u8], root_index: DataIndex, max_index: DataIndex) -> Self { + Self { + root_index, + max_index, + phantom: PhantomData, + } + } + + pub fn get_root_index(&self) -> DataIndex { + self.root_index + } + + pub fn get_max_index(&self) -> DataIndex { + self.max_index + } + + pub fn get_next_lower_index(&self, _index: DataIndex) -> DataIndex { + nondet() + } + + pub fn iter(&self) -> CvtBooksideReadOnlyIterator { + CvtBooksideReadOnlyIterator { calls: 0 } + } +} + +pub struct CvtBookside<'a> { + root_index: DataIndex, + max_index: DataIndex, + phantom: std::marker::PhantomData<&'a mut [u8]>, +} + +impl<'a> CvtBookside<'a> { + pub fn new(_data: &'a mut [u8], root_index: DataIndex, max_index: DataIndex) -> Self { + Self { + root_index, + max_index, + phantom: PhantomData, + } + } + + pub fn get_root_index(&self) -> DataIndex { + self.root_index + } + + pub fn get_max_index(&self) -> DataIndex { + self.max_index + } + + pub fn remove_by_index(&mut self, index: DataIndex) { + if index == main_bid_order_index() { + release_bid_order(); + } else if index == main_ask_order_index() { + release_ask_order(); + } else { + cvt_assert!(false); + } + } + + pub fn insert(&mut self, index: DataIndex, order: RestingOrder) { + let new_order_node: RBNode = RBNode { + left: nondet(), + right: nondet(), + parent: nondet(), + color: hypertree::Color::Red, + value: order, + payload_type: 0, + _unused_padding: 0, + }; + if index == main_bid_order_index() { + let mut dynamic = [0u8; 8]; + let bid_order_node = get_mut_helper_bid_order(&mut dynamic, index); + *bid_order_node = new_order_node; + take_bid_order(); + } else if index == main_ask_order_index() { + let mut dynamic = [0u8; 8]; + let ask_order_node = get_mut_helper_ask_order(&mut dynamic, index); + *ask_order_node = new_order_node; + take_ask_order(); + } else { + cvt_assert!(false); + } + } +} diff --git a/programs/manifest/src/state/cvt_munge.rs b/programs/manifest/src/state/cvt_munge.rs new file mode 100644 index 000000000..a564654ee --- /dev/null +++ b/programs/manifest/src/state/cvt_munge.rs @@ -0,0 +1,5 @@ +#[path = "cvt_db_mock.rs"] +#[cfg(feature = "certora")] +mod cvt_db_mock; +#[cfg(feature = "certora")] +pub use cvt_db_mock::*; diff --git a/programs/manifest/src/state/global.rs b/programs/manifest/src/state/global.rs index 10f9a2252..b3471b536 100644 --- a/programs/manifest/src/state/global.rs +++ b/programs/manifest/src/state/global.rs @@ -12,11 +12,10 @@ use hypertree::{ HyperTreeWriteOperations, RBNode, RedBlackTree, RedBlackTreeReadOnly, NIL, }; use shank::ShankType; -use solana_program::{entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey}; +use solana_program::{entrypoint::ProgramResult, pubkey::Pubkey}; use static_assertions::const_assert_eq; use crate::{ - program::ManifestError, quantities::{GlobalAtoms, WrapperU64}, require, validation::{get_global_address, get_global_vault_address, ManifestAccount}, @@ -199,7 +198,7 @@ impl ManifestAccount for GlobalFixed { // Check the discriminant to make sure it is a global account. require!( self.discriminant == GLOBAL_FIXED_DISCRIMINANT, - ProgramError::InvalidAccountData, + solana_program::program_error::ProgramError::InvalidAccountData, "Invalid market discriminant actual: {} expected: {}", self.discriminant, GLOBAL_FIXED_DISCRIMINANT @@ -269,7 +268,7 @@ impl, Dynamic: DerefOrBorrow<[u8]>> get_global_trader(fixed, dynamic, trader); require!( existing_global_trader_opt.is_some(), - ManifestError::MissingGlobal, + crate::program::ManifestError::MissingGlobal, "Could not find global trader for {}", trader )?; @@ -287,7 +286,7 @@ impl, Dynamic: DerefOrBorrow<[u8]>> require!( existing_deposit_index == fixed.global_deposits_max_index, - ManifestError::GlobalInsufficient, + crate::program::ManifestError::GlobalInsufficient, "Only can remove trader with lowest deposit" )?; @@ -310,7 +309,7 @@ impl, Dynamic: DerefOrBorrowMut<[u8]>> require!( fixed.free_list_head_index == NIL, - ManifestError::InvalidFreeList, + crate::program::ManifestError::InvalidFreeList, "Expected empty free list, but expand wasnt needed", )?; @@ -331,7 +330,7 @@ impl, Dynamic: DerefOrBorrowMut<[u8]>> get_mut_global_deposit(fixed, dynamic, trader); require!( global_deposit_opt.is_some(), - ManifestError::MissingGlobal, + crate::program::ManifestError::MissingGlobal, "Could not find global deposit for {}", trader )?; @@ -352,7 +351,7 @@ impl, Dynamic: DerefOrBorrowMut<[u8]>> require!( global_trader_tree.lookup_index(&global_trader) == NIL, - ManifestError::AlreadyClaimedSeat, + crate::program::ManifestError::AlreadyClaimedSeat, "Already claimed global trader seat", )?; @@ -360,7 +359,7 @@ impl, Dynamic: DerefOrBorrowMut<[u8]>> fixed.global_traders_root_index = global_trader_tree.get_root_index(); require!( fixed.num_seats_claimed < MAX_GLOBAL_SEATS, - ManifestError::TooManyGlobalSeats, + crate::program::ManifestError::TooManyGlobalSeats, "There is a strict limit on number of seats available in a global, use evict", )?; @@ -391,7 +390,7 @@ impl, Dynamic: DerefOrBorrowMut<[u8]>> get_global_trader(fixed, dynamic, existing_trader); require!( existing_global_trader_opt.is_some(), - ManifestError::MissingGlobal, + crate::program::ManifestError::MissingGlobal, "Could not find global trader for {}", existing_trader )?; @@ -401,7 +400,7 @@ impl, Dynamic: DerefOrBorrowMut<[u8]>> get_mut_global_deposit(fixed, dynamic, existing_trader); require!( existing_global_deposit_opt.is_some(), - ManifestError::MissingGlobal, + crate::program::ManifestError::MissingGlobal, "Could not find global deposit for {}", existing_trader )?; @@ -410,7 +409,7 @@ impl, Dynamic: DerefOrBorrowMut<[u8]>> let existing_global_atoms_deposited: GlobalAtoms = existing_global_deposit.balance_atoms; require!( existing_global_atoms_deposited == GlobalAtoms::ZERO, - ManifestError::GlobalInsufficient, + crate::program::ManifestError::GlobalInsufficient, "Error in emptying the existing global", )?; @@ -429,7 +428,7 @@ impl, Dynamic: DerefOrBorrowMut<[u8]>> GlobalTraderTree::new(dynamic, fixed.global_traders_root_index, NIL); require!( existing_deposit_index == fixed.global_deposits_max_index, - ManifestError::GlobalInsufficient, + crate::program::ManifestError::GlobalInsufficient, "Only can remove trader with lowest deposit" )?; let new_global_trader: GlobalTrader = @@ -440,7 +439,7 @@ impl, Dynamic: DerefOrBorrowMut<[u8]>> // Cannot claim an extra seat. require!( global_trader_tree.lookup_index(&new_global_trader) == NIL, - ManifestError::AlreadyClaimedSeat, + crate::program::ManifestError::AlreadyClaimedSeat, "Already claimed global trader seat", )?; @@ -491,7 +490,7 @@ impl, Dynamic: DerefOrBorrowMut<[u8]>> get_mut_global_deposit(fixed, dynamic, global_trade_owner); require!( global_deposit_opt.is_some(), - ManifestError::MissingGlobal, + crate::program::ManifestError::MissingGlobal, "Could not find global deposit for {}", global_trade_owner )?; @@ -503,7 +502,7 @@ impl, Dynamic: DerefOrBorrowMut<[u8]>> // an informational safety check. require!( num_global_atoms <= global_atoms_deposited, - ManifestError::GlobalInsufficient, + crate::program::ManifestError::GlobalInsufficient, "Insufficient funds for global order needed {} has {}", num_global_atoms, global_atoms_deposited @@ -520,7 +519,7 @@ impl, Dynamic: DerefOrBorrowMut<[u8]>> get_mut_global_deposit(fixed, dynamic, trader); require!( global_deposit_opt.is_some(), - ManifestError::MissingGlobal, + crate::program::ManifestError::MissingGlobal, "Could not find global deposit for {}", trader )?; @@ -537,7 +536,7 @@ impl, Dynamic: DerefOrBorrowMut<[u8]>> get_mut_global_deposit(fixed, dynamic, trader); require!( global_deposit_opt.is_some(), - ManifestError::MissingGlobal, + crate::program::ManifestError::MissingGlobal, "Could not find global deposit for {}", trader )?; diff --git a/programs/manifest/src/state/market.rs b/programs/manifest/src/state/market.rs index 30f14d133..62d2c57b9 100644 --- a/programs/manifest/src/state/market.rs +++ b/programs/manifest/src/state/market.rs @@ -1,8 +1,18 @@ +#[cfg(feature = "certora")] +use { + crate::certora::hooks::*, crate::certora::summaries::impact_base_atoms::impact_base_atoms, + hook_macro::cvt_hook_end, nondet::nondet, +}; + use bytemuck::{Pod, Zeroable}; use hypertree::{ - get_helper, get_mut_helper, trace, DataIndex, FreeList, FreeListNode, Get, - HyperTreeReadOperations, HyperTreeValueIteratorTrait, HyperTreeWriteOperations, PodBool, - RBNode, RedBlackTree, RedBlackTreeReadOnly, NIL, + get_helper, get_mut_helper, is_not_nil, trace, DataIndex, FreeList, FreeListNode, Get, PodBool, + RBNode, NIL, +}; +#[cfg(not(feature = "certora"))] +use hypertree::{ + HyperTreeReadOperations, HyperTreeValueIteratorTrait, HyperTreeWriteOperations, RedBlackTree, + RedBlackTreeReadOnly, }; use shank::ShankType; use solana_program::{entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey}; @@ -35,6 +45,60 @@ use super::{ MARKET_FREE_LIST_BLOCK_SIZE, }; +#[path = "market_helpers.rs"] +pub mod market_helpers; +use market_helpers::*; + +#[path = "cvt_munge.rs"] +#[cfg(feature = "certora")] +mod cvt_munge; +#[cfg(feature = "certora")] +pub use cvt_munge::*; + +#[cfg(not(feature = "certora"))] +mod helpers { + use super::*; + /// Read a `RBNode` in an array of data at a given index. + pub fn get_helper_seat(data: &[u8], index: DataIndex) -> &RBNode { + get_helper::>(data, index) + } + /// Read a `RBNode` in an array of data at a given index. + pub fn get_mut_helper_seat(data: &mut [u8], index: DataIndex) -> &mut RBNode { + get_mut_helper::>(data, index) + } + + pub fn get_helper_order(data: &[u8], index: DataIndex) -> &RBNode { + get_helper::>(data, index) + } + pub fn get_mut_helper_order(data: &mut [u8], index: DataIndex) -> &mut RBNode { + get_mut_helper::>(data, index) + } + + pub fn get_helper_bid_order(data: &[u8], index: DataIndex) -> &RBNode { + get_helper::>(data, index) + } + pub fn get_mut_helper_bid_order( + data: &mut [u8], + index: DataIndex, + ) -> &mut RBNode { + get_mut_helper::>(data, index) + } + + pub fn get_helper_ask_order(data: &[u8], index: DataIndex) -> &RBNode { + get_helper::>(data, index) + } + pub fn get_mut_helper_ask_order( + data: &mut [u8], + index: DataIndex, + ) -> &mut RBNode { + get_mut_helper::>(data, index) + } +} + +#[cfg(not(feature = "certora"))] +pub use helpers::*; + +#[derive(Clone)] pub struct AddOrderToMarketArgs<'a, 'info> { pub market: Pubkey, pub trader_index: DataIndex, @@ -56,7 +120,7 @@ pub struct AddOrderToMarketResult { #[repr(C, packed)] #[derive(Default, Copy, Clone, Pod, Zeroable)] -struct MarketUnusedFreeListPadding { +pub struct MarketUnusedFreeListPadding { _padding: [u64; 9], _padding2: [u8; 4], } @@ -120,9 +184,27 @@ pub struct MarketFixed { /// Use at your own risk. quote_volume: QuoteAtoms, + // These are not included in the normal usage because they are informational + // only and not worth the CU. + #[cfg(feature = "certora")] + /// Base tokens reserved for seats + withdrawable_base_atoms: BaseAtoms, + #[cfg(feature = "certora")] + /// Quote tokens reserved for seats + withdrawable_quote_atoms: QuoteAtoms, + #[cfg(feature = "certora")] + /// Base tokens reserved for non-global orders + pub orderbook_base_atoms: BaseAtoms, + #[cfg(feature = "certora")] + /// Quote tokens reserved for non-global orders + pub orderbook_quote_atoms: QuoteAtoms, + #[cfg(feature = "certora")] + _padding3: [u64; 4], + // Unused padding. Saved in case a later version wants to be backwards // compatible. Also, it is nice to have the fixed size be a round number, // 256 bytes. + #[cfg(not(feature = "certora"))] _padding3: [u64; 8], } const_assert_eq!( @@ -181,10 +263,60 @@ impl MarketFixed { asks_root_index: NIL, asks_best_index: NIL, claimed_seats_root_index: NIL, + #[cfg(not(feature = "certora"))] free_list_head_index: NIL, + #[cfg(feature = "certora")] + // non NIL + free_list_head_index: 0, _padding2: [0; 1], quote_volume: QuoteAtoms::ZERO, + #[cfg(not(feature = "certora"))] _padding3: [0; 8], + #[cfg(feature = "certora")] + withdrawable_base_atoms: BaseAtoms::new(0), + #[cfg(feature = "certora")] + withdrawable_quote_atoms: QuoteAtoms::new(0), + #[cfg(feature = "certora")] + orderbook_base_atoms: BaseAtoms::new(0), + #[cfg(feature = "certora")] + orderbook_quote_atoms: QuoteAtoms::new(0), + #[cfg(feature = "certora")] + _padding3: [0; 4], + } + } + + #[cfg(feature = "certora")] + /** All fields are set to nondeterministic values except indexes to the tree **/ + pub fn new_nondet() -> Self { + let claimed_seats_root_index = nondet(); + cvt::cvt_assume!(claimed_seats_root_index == NIL); + MarketFixed { + discriminant: MARKET_FIXED_DISCRIMINANT, + version: 0, + base_mint_decimals: nondet(), + quote_mint_decimals: nondet(), + base_vault_bump: nondet(), + quote_vault_bump: nondet(), + _padding1: [0; 3], + base_mint: nondet(), + quote_mint: nondet(), + base_vault: nondet(), + quote_vault: nondet(), + order_sequence_number: nondet(), + num_bytes_allocated: nondet(), + bids_root_index: NIL, + bids_best_index: NIL, + asks_root_index: NIL, + asks_best_index: NIL, + claimed_seats_root_index, + free_list_head_index: 0, + _padding2: [0; 1], + quote_volume: QuoteAtoms::ZERO, + withdrawable_base_atoms: BaseAtoms::new(nondet()), + withdrawable_quote_atoms: QuoteAtoms::new(nondet()), + orderbook_base_atoms: BaseAtoms::new(nondet()), + orderbook_quote_atoms: QuoteAtoms::new(nondet()), + _padding3: [0; 4], } } @@ -230,6 +362,23 @@ impl MarketFixed { self.asks_best_index } + #[cfg(feature = "certora")] + pub fn get_withdrawable_base_atoms(&self) -> BaseAtoms { + self.withdrawable_base_atoms + } + #[cfg(feature = "certora")] + pub fn get_withdrawable_quote_atoms(&self) -> QuoteAtoms { + self.withdrawable_quote_atoms + } + #[cfg(feature = "certora")] + pub fn get_orderbook_base_atoms(&self) -> BaseAtoms { + self.orderbook_base_atoms + } + #[cfg(feature = "certora")] + pub fn get_orderbook_quote_atoms(&self) -> QuoteAtoms { + self.orderbook_quote_atoms + } + // Used in benchmark pub fn has_free_block(&self) -> bool { self.free_list_head_index != NIL @@ -256,10 +405,27 @@ pub type MarketRef<'a> = DynamicAccount<&'a MarketFixed, &'a [u8]>; /// Full market reference type. pub type MarketRefMut<'a> = DynamicAccount<&'a mut MarketFixed, &'a mut [u8]>; -pub type ClaimedSeatTree<'a> = RedBlackTree<'a, ClaimedSeat>; -pub type ClaimedSeatTreeReadOnly<'a> = RedBlackTreeReadOnly<'a, ClaimedSeat>; -pub type Bookside<'a> = RedBlackTree<'a, RestingOrder>; -pub type BooksideReadOnly<'a> = RedBlackTreeReadOnly<'a, RestingOrder>; +#[cfg(not(feature = "certora"))] +mod types { + use super::*; + pub type ClaimedSeatTree<'a> = RedBlackTree<'a, ClaimedSeat>; + pub type ClaimedSeatTreeReadOnly<'a> = RedBlackTreeReadOnly<'a, ClaimedSeat>; + pub type Bookside<'a> = RedBlackTree<'a, RestingOrder>; + pub type BooksideReadOnly<'a> = RedBlackTreeReadOnly<'a, RestingOrder>; +} +#[cfg(not(feature = "certora"))] +pub use types::*; + +#[cfg(all(feature = "certora"))] +mod cvt_mock_types { + use super::*; + pub type ClaimedSeatTree<'a> = CvtClaimedSeatTree<'a>; + pub type ClaimedSeatTreeReadOnly<'a> = CvtClaimedSeatTreeReadOnly<'a>; + pub type Bookside<'a> = CvtBookside<'a>; + pub type BooksideReadOnly<'a> = CvtBooksideReadOnly<'a>; +} +#[cfg(feature = "certora")] +pub use cvt_mock_types::*; // This generic impl covers MarketRef, MarketRefMut and other // DynamicAccount variants that allow read access. @@ -332,7 +498,7 @@ impl, Dynamic: DerefOrBorrow<[u8]>> )?; // Stop walking if missing the needed global account. - if self.is_missing_global_account(resting_order, is_bid, global_trade_accounts_opts) { + if self.is_missing_global_account(&resting_order, is_bid, global_trade_accounts_opts) { break; } @@ -365,7 +531,21 @@ impl, Dynamic: DerefOrBorrow<[u8]>> return Ok(total_matched_quote_atoms); } + // Simplified version for certora. Those checks are actually stronger than + // needed since it shows invariants hold on swap even when impact_base_atoms + // returns a wrong value. + #[cfg(feature = "certora")] + pub fn impact_base_atoms( + &self, + is_bid: bool, + limit_quote_atoms: QuoteAtoms, + global_trade_accounts_opts: &[Option; 2], + ) -> Result { + impact_base_atoms(self, is_bid, limit_quote_atoms, global_trade_accounts_opts) + } + /// How many base atoms you get when you trade in limit_quote_atoms. + #[cfg(not(feature = "certora"))] pub fn impact_base_atoms( &self, is_bid: bool, @@ -450,11 +630,18 @@ impl, Dynamic: DerefOrBorrow<[u8]>> return Ok(total_matched_base_atoms); } + #[cfg(not(feature = "certora"))] pub fn get_order_by_index(&self, index: DataIndex) -> &RestingOrder { let DynamicAccount { dynamic, .. } = self.borrow_market(); &get_helper::>(dynamic, index).get_value() } + #[cfg(feature = "certora")] + pub fn get_order_by_index(&self, index: DataIndex) -> &RestingOrder { + let DynamicAccount { dynamic, .. } = self.borrow_market(); + &get_helper_order(dynamic, index).get_value() + } + pub fn get_trader_balance(&self, trader: &Pubkey) -> (BaseAtoms, QuoteAtoms) { let DynamicAccount { fixed, dynamic } = self.borrow_market(); @@ -462,8 +649,7 @@ impl, Dynamic: DerefOrBorrow<[u8]>> ClaimedSeatTreeReadOnly::new(dynamic, fixed.claimed_seats_root_index, NIL); let trader_index: DataIndex = claimed_seats_tree.lookup_index(&ClaimedSeat::new_empty(*trader)); - let claimed_seat: &ClaimedSeat = - get_helper::>(dynamic, trader_index).get_value(); + let claimed_seat: &ClaimedSeat = get_helper_seat(dynamic, trader_index).get_value(); ( claimed_seat.base_withdrawable_balance, claimed_seat.quote_withdrawable_balance, @@ -473,9 +659,7 @@ impl, Dynamic: DerefOrBorrow<[u8]>> pub fn get_trader_key_by_index(&self, index: DataIndex) -> &Pubkey { let DynamicAccount { dynamic, .. } = self.borrow_market(); - &get_helper::>(dynamic, index) - .get_value() - .trader + &get_helper_seat(dynamic, index).get_value().trader } pub fn get_trader_voume(&self, trader: &Pubkey) -> QuoteAtoms { @@ -485,8 +669,7 @@ impl, Dynamic: DerefOrBorrow<[u8]>> ClaimedSeatTreeReadOnly::new(dynamic, fixed.claimed_seats_root_index, NIL); let trader_index: DataIndex = claimed_seats_tree.lookup_index(&ClaimedSeat::new_empty(*trader)); - let claimed_seat: &ClaimedSeat = - get_helper::>(dynamic, trader_index).get_value(); + let claimed_seat: &ClaimedSeat = get_helper_seat(dynamic, trader_index).get_value(); claimed_seat.quote_volume } @@ -606,7 +789,7 @@ impl< pub fn claim_seat(&mut self, trader: &Pubkey) -> ProgramResult { let DynamicAccount { fixed, dynamic } = self.borrow_mut(); - let free_address: DataIndex = get_free_address_on_market_fixed(fixed, dynamic); + let free_address: DataIndex = get_free_address_on_market_fixed_for_seat(fixed, dynamic); let mut claimed_seats_tree: ClaimedSeatTree = ClaimedSeatTree::new(dynamic, fixed.claimed_seats_root_index, NIL); @@ -617,8 +800,14 @@ impl< ManifestError::AlreadyClaimedSeat, "Already claimed seat", )?; - claimed_seats_tree.insert(free_address, claimed_seat); + // theoretically we need to update the total withdrawable amount, but it's always 0 here. + // but let's check here that the assumption holds. + #[cfg(feature = "certora")] + { + cvt::cvt_assert!(claimed_seat.base_withdrawable_balance == BaseAtoms::new(0)); + cvt::cvt_assert!(claimed_seat.quote_withdrawable_balance == QuoteAtoms::new(0)); + } fixed.claimed_seats_root_index = claimed_seats_tree.get_root_index(); get_mut_helper::>(dynamic, free_address) @@ -639,11 +828,7 @@ impl< fixed.claimed_seats_root_index = claimed_seats_tree.get_root_index(); // Put back seat on free list. - let mut free_list: FreeList = - FreeList::new(dynamic, fixed.free_list_head_index); - free_list.add(trader_seat_index); - fixed.free_list_head_index = trader_seat_index; - + release_address_on_market_fixed_for_seat(fixed, dynamic, trader_seat_index); Ok(()) } @@ -654,13 +839,12 @@ impl< is_base: bool, ) -> ProgramResult { require!( - trader_index != NIL, + is_not_nil!(trader_index), ManifestError::InvalidDepositAccounts, "No seat initialized", )?; - - let DynamicAccount { dynamic, .. } = self.borrow_mut(); - update_balance(dynamic, trader_index, is_base, true, amount_atoms)?; + let DynamicAccount { fixed, dynamic } = self.borrow_mut(); + update_balance(fixed, dynamic, trader_index, is_base, true, amount_atoms)?; Ok(()) } @@ -670,11 +854,18 @@ impl< amount_atoms: u64, is_base: bool, ) -> ProgramResult { - let DynamicAccount { dynamic, .. } = self.borrow_mut(); - update_balance(dynamic, trader_index, is_base, false, amount_atoms)?; + let DynamicAccount { fixed, dynamic } = self.borrow_mut(); + update_balance(fixed, dynamic, trader_index, is_base, false, amount_atoms)?; Ok(()) } + pub fn place_order_( + &mut self, + args: AddOrderToMarketArgs, + ) -> Result { + market_helpers::place_order_helper(self, args) + } + /// Place an order and update the market /// /// 1. Check the order against the opposite bookside @@ -711,7 +902,7 @@ impl< let mut total_quote_atoms_traded: QuoteAtoms = QuoteAtoms::ZERO; let mut remaining_base_atoms: BaseAtoms = num_base_atoms; - while remaining_base_atoms > BaseAtoms::ZERO && current_maker_order_index != NIL { + while remaining_base_atoms > BaseAtoms::ZERO && is_not_nil!(current_maker_order_index) { let maker_order: &RestingOrder = get_helper::>(dynamic, current_maker_order_index).get_value(); @@ -770,13 +961,10 @@ impl< // If it is a global order, just in time bring the funds over, or // remove from the tree and continue on to the next order. - let maker: Pubkey = - get_helper::>(dynamic, maker_order.get_trader_index()) - .get_value() - .trader; - let taker: Pubkey = get_helper::>(dynamic, trader_index) + let maker: Pubkey = get_helper_seat(dynamic, maker_order.get_trader_index()) .get_value() .trader; + let taker: Pubkey = get_helper_seat(dynamic, trader_index).get_value().trader; let is_global: bool = maker_order.is_global(); if is_global { @@ -804,6 +992,7 @@ impl< base_atoms_traded.as_u64() }), )?; + if !has_enough_tokens { let next_maker_order_index: DataIndex = get_next_candidate_match_index( fixed, @@ -856,6 +1045,7 @@ impl< true, )?; update_balance( + fixed, dynamic, maker_trader_index, is_bid, @@ -869,6 +1059,7 @@ impl< // Increase maker from the matched amount in the trade. update_balance( + fixed, dynamic, maker_trader_index, !is_bid, @@ -881,6 +1072,7 @@ impl< )?; // Decrease taker update_balance( + fixed, dynamic, trader_index, !is_bid, @@ -893,6 +1085,7 @@ impl< )?; // Increase taker update_balance( + fixed, dynamic, trader_index, is_bid, @@ -931,12 +1124,11 @@ impl< .get_order_type() == OrderType::Global { - let global_trade_accounts_opt: &Option = if is_bid { - &global_trade_accounts_opts[0] + if is_bid { + remove_from_global(&global_trade_accounts_opts[0])?; } else { - &global_trade_accounts_opts[1] - }; - remove_from_global(&global_trade_accounts_opt)?; + remove_from_global(&global_trade_accounts_opts[1])?; + } } let next_maker_order_index: DataIndex = get_next_candidate_match_index( @@ -954,10 +1146,14 @@ impl< remaining_base_atoms = remaining_base_atoms.checked_sub(base_atoms_traded)?; current_maker_order_index = next_maker_order_index; } else { + #[cfg(feature = "certora")] + remove_from_orderbook_balance(fixed, dynamic, current_maker_order_index); let maker_order: &mut RestingOrder = get_mut_helper::>(dynamic, current_maker_order_index) .get_mut_value(); maker_order.reduce(base_atoms_traded)?; + #[cfg(feature = "certora")] + add_to_orderbook_balance(fixed, dynamic, current_maker_order_index); remaining_base_atoms = BaseAtoms::ZERO; break; } @@ -994,6 +1190,24 @@ impl< } /// Rest the remaining order onto the market in a RestingOrder. + #[cfg(feature = "certora")] + pub fn certora_rest_remaining( + &mut self, + args: AddOrderToMarketArgs, + remaining_base_atoms: BaseAtoms, + order_sequence_number: u64, + total_base_atoms_traded: BaseAtoms, + total_quote_atoms_traded: QuoteAtoms, + ) -> Result { + self.rest_remaining( + args, + remaining_base_atoms, + order_sequence_number, + total_base_atoms_traded, + total_quote_atoms_traded, + ) + } + fn rest_remaining( &mut self, args: AddOrderToMarketArgs, @@ -1014,7 +1228,11 @@ impl< let DynamicAccount { fixed, dynamic } = self.borrow_mut(); // Put the remaining in an order on the other bookside. - let free_address: DataIndex = get_free_address_on_market_fixed(fixed, dynamic); + let free_address: DataIndex = if is_bid { + get_free_address_on_market_fixed_for_bid_order(fixed, dynamic) + } else { + get_free_address_on_market_fixed_for_ask_order(fixed, dynamic) + }; let resting_order: RestingOrder = RestingOrder::new( trader_index, @@ -1027,21 +1245,28 @@ impl< )?; if resting_order.is_global() { - let global_trade_accounts_opt: &Option = if is_bid { - &global_trade_accounts_opts[1] + if is_bid { + let global_trade_account_opt = &global_trade_accounts_opts[1]; + require!( + global_trade_account_opt.is_some(), + ManifestError::MissingGlobal, + "Missing global accounts when adding a global", + )?; + try_to_add_to_global(&global_trade_account_opt.as_ref().unwrap(), &resting_order)?; } else { - &global_trade_accounts_opts[0] - }; - require!( - global_trade_accounts_opt.is_some(), - ManifestError::MissingGlobal, - "Missing global accounts when adding a global", - )?; - try_to_add_to_global(&global_trade_accounts_opt.as_ref().unwrap(), &resting_order)?; + let global_trade_account_opt = &global_trade_accounts_opts[0]; + require!( + global_trade_account_opt.is_some(), + ManifestError::MissingGlobal, + "Missing global accounts when adding a global", + )?; + try_to_add_to_global(&global_trade_account_opt.as_ref().unwrap(), &resting_order)?; + } } else { // Place the remaining. // Rounds up quote atoms so price can be rounded in favor of taker update_balance( + fixed, dynamic, trader_index, !is_bid, @@ -1055,8 +1280,7 @@ impl< } insert_order_into_tree(is_bid, fixed, dynamic, free_address, &resting_order); - get_mut_helper::>(dynamic, free_address) - .set_payload_type(MarketDataTreeNodeType::RestingOrder as u8); + set_payload_order(dynamic, free_address); Ok(AddOrderToMarketResult { order_sequence_number, @@ -1076,56 +1300,152 @@ impl< let DynamicAccount { fixed, dynamic } = self.borrow_mut(); let mut index_to_remove: DataIndex = NIL; - for is_searching_bids in [false, true] { - let tree: BooksideReadOnly = if is_searching_bids { - BooksideReadOnly::new(dynamic, fixed.bids_root_index, fixed.bids_best_index) - } else { - BooksideReadOnly::new(dynamic, fixed.asks_root_index, fixed.asks_best_index) - }; - for (index, resting_order) in tree.iter::() { - if resting_order.get_sequence_number() == order_sequence_number { - require!( - resting_order.get_trader_index() == trader_index, - ManifestError::InvalidCancel, - "Cannot cancel for another trader", - )?; - require!( - index_to_remove == NIL, - ManifestError::InvalidCancel, - "Book is broken, matched multiple orders", - )?; - index_to_remove = index; - } + + // One iteration to find the index to cancel in the ask side. + let tree: BooksideReadOnly = + BooksideReadOnly::new(dynamic, fixed.asks_root_index, fixed.asks_best_index); + for (index, resting_order) in tree.iter::() { + if resting_order.get_sequence_number() == order_sequence_number { + require!( + resting_order.get_trader_index() == trader_index, + ManifestError::InvalidCancel, + "Cannot cancel for another trader", + )?; + require!( + index_to_remove == NIL, + ManifestError::InvalidCancel, + "Book is broken, matched multiple orders", + )?; + index_to_remove = index; } - if index_to_remove != NIL { - // Cancel order by index will update balances. - self.cancel_order_by_index(index_to_remove, global_trade_accounts_opts)?; - return Ok(()); + } + + // Second iteration to find the index to cancel in the bid side. + let tree: BooksideReadOnly = + BooksideReadOnly::new(dynamic, fixed.bids_root_index, fixed.bids_best_index); + for (index, resting_order) in tree.iter::() { + if resting_order.get_sequence_number() == order_sequence_number { + require!( + resting_order.get_trader_index() == trader_index, + ManifestError::InvalidCancel, + "Cannot cancel for another trader", + )?; + require!( + index_to_remove == NIL, + ManifestError::InvalidCancel, + "Book is broken, matched multiple orders", + )?; + index_to_remove = index; } } + if is_not_nil!(index_to_remove) { + // Cancel order by index will update balances. + self.cancel_order_by_index(index_to_remove, global_trade_accounts_opts)?; + return Ok(()); + } + // Do not fail silently. Err(ManifestError::InvalidCancel.into()) } + #[cfg_attr(feature = "certora", cvt_hook_end(cancel_order_by_index_was_called()))] pub fn cancel_order_by_index( &mut self, order_index: DataIndex, global_trade_accounts_opts: &[Option; 2], ) -> ProgramResult { let DynamicAccount { fixed, dynamic } = self.borrow_mut(); + // TODO: Undo expansion here when it was just + // remove_and_update_balances(fixed, dynamic, order_index, global_trade_accounts_opts)?; + // because the tracking for + + let resting_order: &RestingOrder = get_helper_order(dynamic, order_index).get_value(); + let is_bid: bool = resting_order.get_is_bid(); + let amount_atoms: u64 = if is_bid { + (resting_order + .get_price() + .checked_quote_for_base(resting_order.get_num_base_atoms(), false) + .unwrap()) + .into() + } else { + resting_order.get_num_base_atoms().into() + }; + + // Update the accounting for the order that was just canceled. + if resting_order.is_global() { + if is_bid { + remove_from_global(&global_trade_accounts_opts[1])?; + } else { + remove_from_global(&global_trade_accounts_opts[0])?; + } + + // Certora version was equivalent except it returned early here. + // Thats a bug, but keeping the certora code as close to what they + // wrote as possible. Formal verification does not cover global, so + // that would explain why introducing this bug for formal + // verification does not cause failures. + #[cfg(feature = "certora")] + return Ok(()); + } else { + update_balance( + fixed, + dynamic, + resting_order.get_trader_index(), + !is_bid, + true, + amount_atoms, + )?; + } + remove_order_from_tree_and_free(fixed, dynamic, order_index, is_bid)?; - remove_and_update_balances(fixed, dynamic, order_index, global_trade_accounts_opts)?; Ok(()) } } +fn set_payload_order(dynamic: &mut [u8], free_address: DataIndex) { + get_mut_helper_order(dynamic, free_address) + .set_payload_type(MarketDataTreeNodeType::RestingOrder as u8); +} + +#[cfg(feature = "certora")] +fn add_to_orderbook_balance(fixed: &mut MarketFixed, dynamic: &mut [u8], order_index: DataIndex) { + // Update the sum of orderbook balance. + let resting_order = get_helper_order(dynamic, order_index).get_value(); + let (base_amount, quote_amount) = match resting_order.get_orderbook_atoms() { + Ok(result) => result, + // make errors nondet(), so we can see the violation + Err(_) => (nondet(), nondet()), + }; + fixed.orderbook_base_atoms = fixed.orderbook_base_atoms.saturating_add(base_amount); + fixed.orderbook_quote_atoms = fixed.orderbook_quote_atoms.saturating_add(quote_amount); +} + +#[cfg(feature = "certora")] +fn remove_from_orderbook_balance( + fixed: &mut MarketFixed, + dynamic: &mut [u8], + order_index: DataIndex, +) { + // Update the sum of orderbook balance. + let resting_order = get_helper_order(dynamic, order_index).get_value(); + let (base_amount, quote_amount) = match resting_order.get_orderbook_atoms() { + Ok(result) => result, + // make errors nondet(), so we can see the violation + Err(_) => (nondet(), nondet()), + }; + fixed.orderbook_base_atoms = fixed.orderbook_base_atoms.saturating_sub(base_amount); + fixed.orderbook_quote_atoms = fixed.orderbook_quote_atoms.saturating_sub(quote_amount); +} + fn remove_order_from_tree( fixed: &mut MarketFixed, dynamic: &mut [u8], order_index: DataIndex, is_bids: bool, ) -> ProgramResult { + #[cfg(feature = "certora")] + remove_from_orderbook_balance(fixed, dynamic, order_index); let mut tree: Bookside = if is_bids { Bookside::new(dynamic, fixed.bids_root_index, fixed.bids_best_index) } else { @@ -1159,6 +1479,10 @@ fn remove_order_from_tree( } // Remove order from the tree, free the block. +#[cfg_attr( + feature = "certora", + cvt_hook_end(remove_order_from_tree_and_free_was_called()) +)] fn remove_order_from_tree_and_free( fixed: &mut MarketFixed, dynamic: &mut [u8], @@ -1166,24 +1490,41 @@ fn remove_order_from_tree_and_free( is_bids: bool, ) -> ProgramResult { remove_order_from_tree(fixed, dynamic, order_index, is_bids)?; - let mut free_list: FreeList = - FreeList::new(dynamic, fixed.free_list_head_index); - free_list.add(order_index); - fixed.free_list_head_index = order_index; + // Separate release functions because certora needs that. + if is_bids { + release_address_on_market_fixed_for_bid_order(fixed, dynamic, order_index); + } else { + release_address_on_market_fixed_for_ask_order(fixed, dynamic, order_index); + } Ok(()) } -fn update_balance( +#[allow(unused_variables)] +pub fn update_balance( + fixed: &mut MarketFixed, dynamic: &mut [u8], trader_index: DataIndex, is_base: bool, is_increase: bool, amount_atoms: u64, ) -> ProgramResult { - let claimed_seat: &mut ClaimedSeat = - get_mut_helper::>(dynamic, trader_index).get_mut_value(); + let claimed_seat: &mut ClaimedSeat = get_mut_helper_seat(dynamic, trader_index).get_mut_value(); trace!("update_balance_by_trader_index idx:{trader_index} base:{is_base} inc:{is_increase} amount:{amount_atoms}"); + + // Update the sum of withdrawable balance. + // Subtract the current value from the sum here and at the end of the function, add + // the new value to the sum. + #[cfg(feature = "certora")] + { + fixed.withdrawable_base_atoms = fixed + .withdrawable_base_atoms + .saturating_sub(claimed_seat.base_withdrawable_balance); + fixed.withdrawable_quote_atoms = fixed + .withdrawable_quote_atoms + .saturating_sub(claimed_seat.quote_withdrawable_balance); + } + if is_base { if is_increase { claimed_seat.base_withdrawable_balance = claimed_seat @@ -1217,6 +1558,18 @@ fn update_balance( .quote_withdrawable_balance .checked_sub(QuoteAtoms::new(amount_atoms))?; } + + // Update the sum of withdrawable balance, second part. + #[cfg(feature = "certora")] + { + fixed.withdrawable_base_atoms = fixed + .withdrawable_base_atoms + .saturating_add(claimed_seat.base_withdrawable_balance); + fixed.withdrawable_quote_atoms = fixed + .withdrawable_quote_atoms + .saturating_add(claimed_seat.quote_withdrawable_balance); + } + Ok(()) } @@ -1225,8 +1578,7 @@ fn record_volume_by_trader_index( trader_index: DataIndex, amount_atoms: QuoteAtoms, ) { - let claimed_seat: &mut ClaimedSeat = - get_mut_helper::>(dynamic, trader_index).get_mut_value(); + let claimed_seat: &mut ClaimedSeat = get_mut_helper_seat(dynamic, trader_index).get_mut_value(); claimed_seat.quote_volume = claimed_seat.quote_volume.wrapping_add(amount_atoms); } @@ -1244,6 +1596,7 @@ fn insert_order_into_tree( Bookside::new(dynamic, fixed.asks_root_index, fixed.asks_best_index) }; tree.insert(free_address, *resting_order); + if is_bid { trace!( "insert order bid {resting_order:?} root:{}->{} max:{}->{}->{}", @@ -1267,6 +1620,8 @@ fn insert_order_into_tree( fixed.asks_root_index = tree.get_root_index(); fixed.asks_best_index = tree.get_max_index(); } + #[cfg(feature = "certora")] + add_to_orderbook_balance(fixed, dynamic, free_address); } fn get_next_candidate_match_index( @@ -1290,14 +1645,6 @@ fn get_next_candidate_match_index( } } -fn get_free_address_on_market_fixed(fixed: &mut MarketFixed, dynamic: &mut [u8]) -> DataIndex { - let mut free_list: FreeList = - FreeList::new(dynamic, fixed.free_list_head_index); - let free_address: DataIndex = free_list.remove(); - fixed.free_list_head_index = free_list.get_head(); - free_address -} - fn remove_and_update_balances( fixed: &mut MarketFixed, dynamic: &mut [u8], @@ -1305,18 +1652,16 @@ fn remove_and_update_balances( global_trade_accounts_opts: &[Option; 2], ) -> ProgramResult { let resting_order_to_remove: &RestingOrder = - get_helper::>(dynamic, order_to_remove_index).get_value(); + get_helper_order(dynamic, order_to_remove_index).get_value(); let order_to_remove_is_bid: bool = resting_order_to_remove.get_is_bid(); // Global order balances are accounted for on the global accounts, not on the market. if resting_order_to_remove.is_global() { - let global_trade_accounts_opt: &Option = if order_to_remove_is_bid { - &global_trade_accounts_opts[1] + if order_to_remove_is_bid { + remove_from_global(&global_trade_accounts_opts[1])?; } else { - &global_trade_accounts_opts[0] - }; - - remove_from_global(&global_trade_accounts_opt)?; + remove_from_global(&global_trade_accounts_opts[0])?; + } } else { // Return the exact number of atoms if the resting order is an // ask. If the resting order is bid, multiply by price and round @@ -1331,6 +1676,7 @@ fn remove_and_update_balances( resting_order_to_remove.get_num_base_atoms().as_u64() }; update_balance( + fixed, dynamic, resting_order_to_remove.get_trader_index(), !order_to_remove_is_bid, diff --git a/programs/manifest/src/state/market_helpers.rs b/programs/manifest/src/state/market_helpers.rs new file mode 100644 index 000000000..4e30b5951 --- /dev/null +++ b/programs/manifest/src/state/market_helpers.rs @@ -0,0 +1,553 @@ +#[cfg(not(feature = "certora"))] +mod free_addr_helpers { + use crate::state::market::{MarketFixed, MarketUnusedFreeListPadding}; + use hypertree::{DataIndex, FreeList}; + + pub fn get_free_address_on_market_fixed( + fixed: &mut MarketFixed, + dynamic: &mut [u8], + ) -> DataIndex { + let mut free_list: FreeList = + FreeList::new(dynamic, fixed.free_list_head_index); + let free_address: DataIndex = free_list.remove(); + fixed.free_list_head_index = free_list.get_head(); + free_address + } + + pub fn get_free_address_on_market_fixed_for_seat( + fixed: &mut MarketFixed, + dynamic: &mut [u8], + ) -> DataIndex { + get_free_address_on_market_fixed(fixed, dynamic) + } + + pub fn get_free_address_on_market_fixed_for_bid_order( + fixed: &mut MarketFixed, + dynamic: &mut [u8], + ) -> DataIndex { + get_free_address_on_market_fixed(fixed, dynamic) + } + + pub fn get_free_address_on_market_fixed_for_ask_order( + fixed: &mut MarketFixed, + dynamic: &mut [u8], + ) -> DataIndex { + get_free_address_on_market_fixed(fixed, dynamic) + } + + pub fn release_address_on_market_fixed( + fixed: &mut MarketFixed, + dynamic: &mut [u8], + index: DataIndex, + ) { + let mut free_list: FreeList = + FreeList::new(dynamic, fixed.free_list_head_index); + free_list.add(index); + fixed.free_list_head_index = index; + } + + pub fn release_address_on_market_fixed_for_seat( + fixed: &mut MarketFixed, + dynamic: &mut [u8], + index: DataIndex, + ) { + release_address_on_market_fixed(fixed, dynamic, index); + } + + pub fn release_address_on_market_fixed_for_bid_order( + fixed: &mut MarketFixed, + dynamic: &mut [u8], + index: DataIndex, + ) { + release_address_on_market_fixed(fixed, dynamic, index); + } + + pub fn release_address_on_market_fixed_for_ask_order( + fixed: &mut MarketFixed, + dynamic: &mut [u8], + index: DataIndex, + ) { + release_address_on_market_fixed(fixed, dynamic, index); + } +} + +#[cfg(feature = "certora")] +mod free_addr_helpers { + use crate::state::market::MarketFixed; + + use super::{is_main_seat_free, is_second_seat_free, main_trader_index, second_trader_index}; + use hypertree::DataIndex; + + pub fn get_free_address_on_market_fixed_for_seat( + _fixed: &mut MarketFixed, + _dynamic: &mut [u8], + ) -> DataIndex { + // -- return index of the first available trader + if is_main_seat_free() { + main_trader_index() + } else if is_second_seat_free() { + second_trader_index() + } else { + cvt::cvt_assert!(false); + crate::state::market::NIL + } + } + + pub fn get_free_address_on_market_fixed_for_bid_order( + _fixed: &mut MarketFixed, + _dynamic: &mut [u8], + ) -> DataIndex { + if super::is_bid_order_free() { + super::main_bid_order_index() + } else { + cvt::cvt_assert!(false); + super::NIL + } + } + + pub fn get_free_address_on_market_fixed_for_ask_order( + _fixed: &mut MarketFixed, + _dynamic: &mut [u8], + ) -> DataIndex { + if super::is_ask_order_free() { + super::main_ask_order_index() + } else { + cvt::cvt_assert!(false); + super::NIL + } + } + + pub fn release_address_on_market_fixed_for_seat( + _fixed: &mut MarketFixed, + _dynamic: &mut [u8], + _index: DataIndex, + ) { + } + + pub fn release_address_on_market_fixed_for_bid_order( + _fixed: &mut MarketFixed, + _dynamic: &mut [u8], + _index: DataIndex, + ) { + } + + pub fn release_address_on_market_fixed_for_ask_order( + _fixed: &mut MarketFixed, + _dynamic: &mut [u8], + _index: DataIndex, + ) { + } +} + +pub use free_addr_helpers::*; + +// Refactoring of place_order + +use super::*; + +#[derive(Default, PartialEq)] +pub enum AddOrderStatus { + #[default] + Canceled, + Filled, + PartialFill, + Unmatched, + GlobalSkip, +} + +#[derive(Default)] +pub struct AddOrderToMarketInnerResult { + pub next_order_index: DataIndex, + pub status: AddOrderStatus, +} + +pub struct AddSingleOrderCtx<'a, 'b, 'info> { + pub args: AddOrderToMarketArgs<'b, 'info>, + fixed: &'a mut MarketFixed, + dynamic: &'a mut [u8], + pub now_slot: u32, + pub remaining_base_atoms: BaseAtoms, + pub total_base_atoms_traded: BaseAtoms, + pub total_quote_atoms_traded: QuoteAtoms, +} + +impl<'a, 'b, 'info> AddSingleOrderCtx<'a, 'b, 'info> { + pub fn new( + args: AddOrderToMarketArgs<'b, 'info>, + fixed: &'a mut MarketFixed, + dynamic: &'a mut [u8], + remaining_base_atoms: BaseAtoms, + now_slot: u32, + ) -> Self { + Self { + args, + fixed, + dynamic, + now_slot, + remaining_base_atoms, + total_base_atoms_traded: BaseAtoms::ZERO, + total_quote_atoms_traded: QuoteAtoms::ZERO, + } + } + // TODO: Clean this up or prove that it is the same as market::place_order + pub fn place_single_order( + &mut self, + current_order_index: DataIndex, + ) -> Result { + let fixed: &mut _ = self.fixed; + let dynamic: &mut _ = self.dynamic; + let now_slot = self.now_slot; + let remaining_base_atoms = self.remaining_base_atoms; + + let AddOrderToMarketArgs { + market, + trader_index, + num_base_atoms: _, + price, + is_bid, + last_valid_slot: _, + order_type, + global_trade_accounts_opts, + current_slot: _, + } = self.args; + + let next_order_index: DataIndex = + get_next_candidate_match_index(fixed, dynamic, current_order_index, is_bid); + + let other_order: &RestingOrder = get_helper_order(dynamic, current_order_index).get_value(); + + // Remove the resting order if expired. + if other_order.is_expired(now_slot) { + remove_and_update_balances( + fixed, + dynamic, + current_order_index, + global_trade_accounts_opts, + )?; + return Ok(AddOrderToMarketInnerResult { + next_order_index, + status: AddOrderStatus::Canceled, + ..Default::default() + }); + } + + // Stop trying to match if price no longer satisfies limit. + if (is_bid && other_order.get_price() > price) + || (!is_bid && other_order.get_price() < price) + { + return Ok(AddOrderToMarketInnerResult { + next_order_index: NIL, + status: AddOrderStatus::Unmatched, + ..Default::default() + }); + } + + // Got a match. First make sure we are allowed to match. We check + // inside the matching rather than skipping the matching altogether + // because post only orders should fail, not produce a crossed book. + trace!( + "match {} {order_type:?} {price:?} with {other_order:?}", + if is_bid { "bid" } else { "ask" } + ); + assert_can_take(order_type)?; + + let maker_sequence_number = other_order.get_sequence_number(); + let other_trader_index: DataIndex = other_order.get_trader_index(); + let did_fully_match_resting_order: bool = + remaining_base_atoms >= other_order.get_num_base_atoms(); + let base_atoms_traded: BaseAtoms = if did_fully_match_resting_order { + other_order.get_num_base_atoms() + } else { + remaining_base_atoms + }; + + let matched_price: QuoteAtomsPerBaseAtom = other_order.get_price(); + + // on full fill: round in favor of the taker + // on partial fill: round in favor of the maker + let quote_atoms_traded: QuoteAtoms = matched_price + .checked_quote_for_base(base_atoms_traded, is_bid != did_fully_match_resting_order)?; + + // If it is a global order, just in time bring the funds over, or + // remove from the tree and continue on to the next order. + let maker: Pubkey = get_helper_seat(dynamic, other_order.get_trader_index()) + .get_value() + .trader; + let taker: Pubkey = get_helper_seat(dynamic, trader_index).get_value().trader; + + if other_order.is_global() { + let global_trade_accounts_opt: &Option = if is_bid { + &global_trade_accounts_opts[0] + } else { + &global_trade_accounts_opts[1] + }; + let has_enough_tokens: bool = try_to_move_global_tokens( + global_trade_accounts_opt, + &maker, + GlobalAtoms::new(if is_bid { + quote_atoms_traded.as_u64() + } else { + base_atoms_traded.as_u64() + }), + )?; + if !has_enough_tokens { + remove_and_update_balances( + fixed, + dynamic, + current_order_index, + global_trade_accounts_opts, + )?; + return Ok(AddOrderToMarketInnerResult { + next_order_index, + status: AddOrderStatus::GlobalSkip, + ..Default::default() + }); + } + } + + self.total_base_atoms_traded = self + .total_base_atoms_traded + .checked_add(base_atoms_traded)?; + self.total_quote_atoms_traded = self + .total_quote_atoms_traded + .checked_add(quote_atoms_traded)?; + + // Possibly increase bonus atom maker gets from the rounding the + // quote in their favor. They will get one less than expected when + // cancelling because of rounding, this counters that. This ensures + // that the amount of quote that the maker has credit for when they + // cancel/expire is always the maximum amount that could have been + // used in matching that order. + // Example: + // Maker deposits 11 | Balance: 0 base 11 quote | Orders: [] + // Maker bid for 10@1.15 | Balance: 0 base 0 quote | Orders: [bid 10@1.15] + // Swap 5 base <--> 5 quote | Balance: 5 base 0 quote | Orders: [bid 5@1.15] + // | Balance: 5 base 1 quote | Orders: [bid 5@1.15] + // Maker cancel | Balance: 5 base 6 quote | Orders: [] + // + // The swapper deposited 5 base and withdrew 5 quote. The maker deposited 11 quote. + // If we didnt do this adjustment, there would be an unaccounted for + // quote atom. + // Note that we do not have to do this on the other direction + // because the amount of atoms that a maker needs to support an ask + // is exact. The rounding is always on quote. + if !is_bid { + // These are only used when is_bid, included up here for borrow checker reasons. + let other_order: &RestingOrder = + get_helper_order(dynamic, current_order_index).get_value(); + let previous_maker_quote_atoms_allocated: QuoteAtoms = + matched_price.checked_quote_for_base(other_order.get_num_base_atoms(), true)?; + let new_maker_quote_atoms_allocated: QuoteAtoms = matched_price + .checked_quote_for_base( + other_order + .get_num_base_atoms() + .checked_sub(base_atoms_traded)?, + true, + )?; + update_balance( + fixed, + dynamic, + other_trader_index, + is_bid, + true, + (previous_maker_quote_atoms_allocated + .checked_sub(new_maker_quote_atoms_allocated)? + .checked_sub(quote_atoms_traded)?) + .as_u64(), + )?; + } + + // Certora : the manifest code first increased the maker for the matched amount, + // then decreased the taker. This causes an overflow on withdrawable_balances. + // Thus, we changed it to first decrease the taker, and then increase the maker. + + // Decrease taker + update_balance( + fixed, + dynamic, + trader_index, + !is_bid, + false, + if is_bid { + quote_atoms_traded.into() + } else { + base_atoms_traded.into() + }, + )?; + // Increase maker from the matched amount in the trade. + update_balance( + fixed, + dynamic, + other_trader_index, + !is_bid, + true, + if is_bid { + quote_atoms_traded.into() + } else { + base_atoms_traded.into() + }, + )?; + // Increase taker + update_balance( + fixed, + dynamic, + trader_index, + is_bid, + true, + if is_bid { + base_atoms_traded.into() + } else { + quote_atoms_traded.into() + }, + )?; + + // record maker & taker volume + record_volume_by_trader_index(dynamic, other_trader_index, quote_atoms_traded); + record_volume_by_trader_index(dynamic, trader_index, quote_atoms_traded); + + emit_stack(FillLog { + market, + maker, + taker, + base_atoms: base_atoms_traded, + quote_atoms: quote_atoms_traded, + price: matched_price, + maker_sequence_number, + taker_sequence_number: fixed.order_sequence_number, + taker_is_buy: PodBool::from(is_bid), + base_mint: *fixed.get_base_mint(), + quote_mint: *fixed.get_quote_mint(), + // TODO: Fix this + is_maker_global: PodBool::from(false), + _padding: [0; 14], + })?; + + if did_fully_match_resting_order { + // Get paid for removing a global order. + if get_helper_order(dynamic, current_order_index) + .get_value() + .get_order_type() + == OrderType::Global + { + if is_bid { + remove_from_global(&global_trade_accounts_opts[0])?; + } else { + remove_from_global(&global_trade_accounts_opts[1])?; + } + } + + remove_order_from_tree_and_free(fixed, dynamic, current_order_index, !is_bid)?; + self.remaining_base_atoms = self.remaining_base_atoms.checked_sub(base_atoms_traded)?; + return Ok(AddOrderToMarketInnerResult { + next_order_index, + status: AddOrderStatus::Filled, + ..Default::default() + }); + } else { + #[cfg(feature = "certora")] + remove_from_orderbook_balance(fixed, dynamic, current_order_index); + let other_order: &mut RestingOrder = + get_mut_helper_order(dynamic, current_order_index).get_mut_value(); + other_order.reduce(base_atoms_traded)?; + #[cfg(feature = "certora")] + add_to_orderbook_balance(fixed, dynamic, current_order_index); + self.remaining_base_atoms = BaseAtoms::ZERO; + return Ok(AddOrderToMarketInnerResult { + next_order_index: NIL, + status: AddOrderStatus::PartialFill, + ..Default::default() + }); + } + } +} + +pub fn place_order_helper< + Fixed: DerefOrBorrowMut + DerefOrBorrow, + Dynamic: DerefOrBorrowMut<[u8]> + DerefOrBorrow<[u8]>, +>( + self_: &mut DynamicAccount, + args: AddOrderToMarketArgs, +) -> Result { + let AddOrderToMarketArgs { + market: _, + trader_index, + num_base_atoms, + price: _, + is_bid, + last_valid_slot, + order_type, + global_trade_accounts_opts: _, + current_slot, + } = args; + assert_already_has_seat(trader_index)?; + let now_slot: u32 = current_slot.unwrap_or_else(|| get_now_slot()); + + assert_not_already_expired(last_valid_slot, now_slot)?; + + let DynamicAccount { fixed, dynamic } = self_.borrow_mut(); + + let mut current_order_index: DataIndex = if is_bid { + fixed.asks_best_index + } else { + fixed.bids_best_index + }; + + let mut total_base_atoms_traded: BaseAtoms = BaseAtoms::ZERO; + let mut total_quote_atoms_traded: QuoteAtoms = QuoteAtoms::ZERO; + + let mut remaining_base_atoms: BaseAtoms = num_base_atoms; + + let mut ctx: AddSingleOrderCtx = + AddSingleOrderCtx::new(args, fixed, dynamic, remaining_base_atoms, now_slot); + + while remaining_base_atoms > BaseAtoms::ZERO && is_not_nil!(current_order_index) { + // one step of placing an order + let AddOrderToMarketInnerResult { + next_order_index, + status, + } = ctx.place_single_order(current_order_index)?; + + // update global state based on the context + // this ensures that each iteration of the loop updates all + // variables in scope just as it did originally. + current_order_index = next_order_index; + remaining_base_atoms = ctx.remaining_base_atoms; + total_base_atoms_traded = ctx.total_base_atoms_traded; + total_quote_atoms_traded = ctx.total_quote_atoms_traded; + + if status == AddOrderStatus::Unmatched { + break; + } else if status == AddOrderStatus::PartialFill { + break; + } + } + // move out args so that they can be used later + let args: AddOrderToMarketArgs = ctx.args; + // ctx is dead from this point onward + + // Record volume on market + fixed.quote_volume = fixed.quote_volume.wrapping_add(total_quote_atoms_traded); + + // Bump the order sequence number even for orders which do not end up + // resting. + let order_sequence_number: u64 = fixed.order_sequence_number; + fixed.order_sequence_number = order_sequence_number.wrapping_add(1); + + // If there is nothing left to rest, then return before resting. + if !order_type_can_rest(order_type) || remaining_base_atoms == BaseAtoms::ZERO { + return Ok(AddOrderToMarketResult { + order_sequence_number, + order_index: NIL, + base_atoms_traded: total_base_atoms_traded, + quote_atoms_traded: total_quote_atoms_traded, + }); + } + + self_.rest_remaining( + args, + remaining_base_atoms, + order_sequence_number, + total_base_atoms_traded, + total_quote_atoms_traded, + ) +} diff --git a/programs/manifest/src/state/resting_order.rs b/programs/manifest/src/state/resting_order.rs index 7e06a0a6d..389b85d4f 100644 --- a/programs/manifest/src/state/resting_order.rs +++ b/programs/manifest/src/state/resting_order.rs @@ -1,6 +1,8 @@ use std::mem::size_of; use crate::quantities::{BaseAtoms, QuoteAtomsPerBaseAtom}; +#[cfg(feature = "certora")] +use crate::quantities::{QuoteAtoms, WrapperU64}; use borsh::{BorshDeserialize, BorshSerialize}; use bytemuck::{Pod, Zeroable}; use hypertree::{DataIndex, PodBool}; @@ -125,6 +127,12 @@ impl RestingOrder { self.order_type } + #[cfg(feature = "certora")] + pub fn is_global(&self) -> bool { + false + } + + #[cfg(not(feature = "certora"))] pub fn is_global(&self) -> bool { self.order_type == OrderType::Global } @@ -141,6 +149,20 @@ impl RestingOrder { self.is_bid.0 == 1 } + // compute the "value" of an order, i.e. the tokens that are reserved for the trade and + // that will be returned when it is cancelled. + #[cfg(feature = "certora")] + pub fn get_orderbook_atoms(&self) -> Result<(BaseAtoms, QuoteAtoms), ProgramError> { + if self.is_global() { + return Ok((BaseAtoms::new(0), QuoteAtoms::new(0))); + } else if self.get_is_bid() { + let quote_amount = self.num_base_atoms.checked_mul(self.price, true)?; + return Ok((BaseAtoms::new(0), quote_amount)); + } else { + return Ok((self.num_base_atoms, QuoteAtoms::new(0))); + } + } + pub fn reduce(&mut self, size: BaseAtoms) -> ProgramResult { self.num_base_atoms = self.num_base_atoms.checked_sub(size)?; Ok(()) diff --git a/programs/manifest/src/state/utils.rs b/programs/manifest/src/state/utils.rs index 84af3d5f7..161192a60 100644 --- a/programs/manifest/src/state/utils.rs +++ b/programs/manifest/src/state/utils.rs @@ -3,7 +3,7 @@ use std::cell::RefMut; use crate::{ global_vault_seeds_with_bump, logs::{emit_stack, GlobalCleanupLog}, - program::{get_mut_dynamic_account, invoke, ManifestError}, + program::{get_mut_dynamic_account, invoke}, quantities::{GlobalAtoms, WrapperU64}, require, validation::{loaders::GlobalTradeAccounts, MintAccountInfo, TokenAccountInfo, TokenProgram}, @@ -63,6 +63,7 @@ pub(crate) fn get_now_epoch() -> u64 { now_epoch } +#[inline(always)] pub(crate) fn remove_from_global( global_trade_accounts_opt: &Option, ) -> ProgramResult { @@ -159,7 +160,7 @@ pub(crate) fn try_to_add_to_global( pub(crate) fn assert_can_take(order_type: OrderType) -> ProgramResult { require!( order_type_can_take(order_type), - ManifestError::PostOnlyCrosses, + crate::program::ManifestError::PostOnlyCrosses, "Post only order would cross", )?; Ok(()) @@ -168,7 +169,7 @@ pub(crate) fn assert_can_take(order_type: OrderType) -> ProgramResult { pub(crate) fn assert_not_already_expired(last_valid_slot: u32, now_slot: u32) -> ProgramResult { require!( last_valid_slot == NO_EXPIRATION_LAST_VALID_SLOT || last_valid_slot > now_slot, - ManifestError::AlreadyExpired, + crate::program::ManifestError::AlreadyExpired, "Placing an already expired order. now: {} last_valid: {}", now_slot, last_valid_slot @@ -179,7 +180,7 @@ pub(crate) fn assert_not_already_expired(last_valid_slot: u32, now_slot: u32) -> pub(crate) fn assert_already_has_seat(trader_index: DataIndex) -> ProgramResult { require!( trader_index != NIL, - ManifestError::AlreadyClaimedSeat, + crate::program::ManifestError::AlreadyClaimedSeat, "Need to claim a seat first", )?; Ok(()) @@ -211,7 +212,7 @@ pub(crate) fn try_to_move_global_tokens<'a, 'info>( ) -> Result { require!( global_trade_accounts_opt.is_some(), - ManifestError::MissingGlobal, + crate::program::ManifestError::MissingGlobal, "Missing global accounts when adding a global", )?; let global_trade_accounts: &GlobalTradeAccounts = &global_trade_accounts_opt.as_ref().unwrap(); @@ -259,7 +260,7 @@ pub(crate) fn try_to_move_global_tokens<'a, 'info>( if *token_program.key == spl_token_2022::id() { require!( mint_opt.is_some(), - ManifestError::MissingGlobal, + crate::program::ManifestError::MissingGlobal, "Missing global mint", )?; diff --git a/programs/manifest/src/validation/loaders.rs b/programs/manifest/src/validation/loaders.rs index 9b2e20b38..2879df626 100644 --- a/programs/manifest/src/validation/loaders.rs +++ b/programs/manifest/src/validation/loaders.rs @@ -17,6 +17,9 @@ use crate::{ use super::{get_vault_address, ManifestAccountInfo, TokenProgram}; +#[cfg(feature = "certora")] +use early_panic::early_panic; + /// CreateMarket account infos pub(crate) struct CreateMarketContext<'a, 'info> { pub payer: Signer<'a, 'info>, @@ -61,6 +64,7 @@ impl<'a, 'info> CreateMarketContext<'a, 'info> { )?; let token_program: TokenProgram = TokenProgram::new(next_account_info(account_iter)?)?; let token_program_22: TokenProgram = TokenProgram::new(next_account_info(account_iter)?)?; + Ok(Self { payer, market, @@ -83,6 +87,7 @@ pub(crate) struct ClaimSeatContext<'a, 'info> { } impl<'a, 'info> ClaimSeatContext<'a, 'info> { + #[cfg_attr(all(feature = "certora", not(feature = "certora-test")), early_panic)] pub fn load(accounts: &'a [AccountInfo<'info>]) -> Result { let account_iter: &mut Iter> = &mut accounts.iter(); @@ -134,6 +139,7 @@ pub(crate) struct DepositContext<'a, 'info> { } impl<'a, 'info> DepositContext<'a, 'info> { + #[cfg_attr(all(feature = "certora", not(feature = "certora-test")), early_panic)] pub fn load(accounts: &'a [AccountInfo<'info>]) -> Result { let account_iter: &mut Iter> = &mut accounts.iter(); @@ -261,6 +267,7 @@ pub(crate) struct SwapContext<'a, 'info> { } impl<'a, 'info> SwapContext<'a, 'info> { + #[cfg_attr(all(feature = "certora", not(feature = "certora-test")), early_panic)] pub fn load(accounts: &'a [AccountInfo<'info>]) -> Result { let account_iter: &mut Iter> = &mut accounts.iter(); @@ -459,84 +466,93 @@ impl<'a, 'info> BatchUpdateContext<'a, 'info> { ManifestAccountInfo::::new(next_account_info(account_iter)?)?; let system_program: Program = Program::new(next_account_info(account_iter)?, &system_program::id())?; - + // Certora version is not mutable. + #[cfg(feature = "certora")] + let global_trade_accounts_opts: [Option>; 2] = [None, None]; + #[cfg(not(feature = "certora"))] let mut global_trade_accounts_opts: [Option>; 2] = [None, None]; - let market_fixed: Ref = market.get_fixed()?; - let base_mint: Pubkey = *market_fixed.get_base_mint(); - let quote_mint: Pubkey = *market_fixed.get_quote_mint(); - let base_vault: Pubkey = *market_fixed.get_base_vault(); - let quote_vault: Pubkey = *market_fixed.get_quote_vault(); - drop(market_fixed); - - for _ in 0..2 { - let next_account_info_or: Result<&AccountInfo<'info>, ProgramError> = - next_account_info(account_iter); - if next_account_info_or.is_ok() { - let mint: MintAccountInfo<'a, 'info> = MintAccountInfo::new(next_account_info_or?)?; - let (index, expected_market_vault_address) = if base_mint == *mint.info.key { - (0, &base_vault) - } else { - require!( - quote_mint == *mint.info.key, - ManifestError::MissingGlobal, - "Unexpected global mint", - )?; - (1, "e_vault) + #[cfg(not(feature = "certora"))] + { + let market_fixed: Ref = market.get_fixed()?; + let base_mint: Pubkey = *market_fixed.get_base_mint(); + let quote_mint: Pubkey = *market_fixed.get_quote_mint(); + let base_vault: Pubkey = *market_fixed.get_base_vault(); + let quote_vault: Pubkey = *market_fixed.get_quote_vault(); + drop(market_fixed); + + for _ in 0..2 { + let next_account_info_or: Result<&AccountInfo<'info>, ProgramError> = + next_account_info(account_iter); + if next_account_info_or.is_ok() { + let mint: MintAccountInfo<'a, 'info> = + MintAccountInfo::new(next_account_info_or?)?; + let (index, expected_market_vault_address) = if base_mint == *mint.info.key { + (0, &base_vault) + } else { + require!( + quote_mint == *mint.info.key, + ManifestError::MissingGlobal, + "Unexpected global mint", + )?; + (1, "e_vault) + }; + + let global_or: Result< + ManifestAccountInfo<'a, 'info, GlobalFixed>, + ProgramError, + > = ManifestAccountInfo::::new(next_account_info(account_iter)?); + + // If a client blindly fills in the global account and vault, + // then handle that case and allow them to try to work without + // the global accounts. + if global_or.is_err() { + let _global_vault: Result<&AccountInfo<'info>, ProgramError> = + next_account_info(account_iter); + let _market_vault: Result<&AccountInfo<'info>, ProgramError> = + next_account_info(account_iter); + let _token_program: Result<&AccountInfo<'info>, ProgramError> = + next_account_info(account_iter); + continue; + } + let global: ManifestAccountInfo<'a, 'info, GlobalFixed> = global_or.unwrap(); + let global_data: Ref<&mut [u8]> = global.data.borrow(); + let global_fixed: &GlobalFixed = get_helper::(&global_data, 0_u32); + let expected_global_vault_address: &Pubkey = global_fixed.get_vault(); + + let global_vault: TokenAccountInfo<'a, 'info> = + TokenAccountInfo::new_with_owner_and_key( + next_account_info(account_iter)?, + mint.info.key, + &expected_global_vault_address, + &expected_global_vault_address, + )?; + drop(global_data); + + let market_vault: TokenAccountInfo<'a, 'info> = + TokenAccountInfo::new_with_owner_and_key( + next_account_info(account_iter)?, + mint.info.key, + &expected_market_vault_address, + &expected_market_vault_address, + )?; + let token_program: TokenProgram<'a, 'info> = + TokenProgram::new(next_account_info(account_iter)?)?; + + global_trade_accounts_opts[index] = Some(GlobalTradeAccounts { + mint_opt: Some(mint), + global, + global_vault_opt: Some(global_vault), + market_vault_opt: Some(market_vault), + token_program_opt: Some(token_program), + system_program: Some(system_program.clone()), + gas_payer_opt: Some(payer.clone()), + gas_receiver_opt: Some(payer.clone()), + market: *market.info.key, + }) }; - - let global_or: Result, ProgramError> = - ManifestAccountInfo::::new(next_account_info(account_iter)?); - - // If a client blindly fills in the global account and vault, - // then handle that case and allow them to try to work without - // the global accounts. - if global_or.is_err() { - let _global_vault: Result<&AccountInfo<'info>, ProgramError> = - next_account_info(account_iter); - let _market_vault: Result<&AccountInfo<'info>, ProgramError> = - next_account_info(account_iter); - let _token_program: Result<&AccountInfo<'info>, ProgramError> = - next_account_info(account_iter); - continue; - } - let global: ManifestAccountInfo<'a, 'info, GlobalFixed> = global_or.unwrap(); - let global_data: Ref<&mut [u8]> = global.data.borrow(); - let global_fixed: &GlobalFixed = get_helper::(&global_data, 0_u32); - let expected_global_vault_address: &Pubkey = global_fixed.get_vault(); - - let global_vault: TokenAccountInfo<'a, 'info> = - TokenAccountInfo::new_with_owner_and_key( - next_account_info(account_iter)?, - mint.info.key, - &expected_global_vault_address, - &expected_global_vault_address, - )?; - drop(global_data); - - let market_vault: TokenAccountInfo<'a, 'info> = - TokenAccountInfo::new_with_owner_and_key( - next_account_info(account_iter)?, - mint.info.key, - &expected_market_vault_address, - &expected_market_vault_address, - )?; - let token_program: TokenProgram<'a, 'info> = - TokenProgram::new(next_account_info(account_iter)?)?; - - global_trade_accounts_opts[index] = Some(GlobalTradeAccounts { - mint_opt: Some(mint), - global, - global_vault_opt: Some(global_vault), - market_vault_opt: Some(market_vault), - token_program_opt: Some(token_program), - system_program: Some(system_program.clone()), - gas_payer_opt: Some(payer.clone()), - gas_receiver_opt: Some(payer.clone()), - market: *market.info.key, - }) - }; + } } Ok(Self { diff --git a/programs/manifest/verify-manifest.py b/programs/manifest/verify-manifest.py new file mode 100755 index 000000000..568003d85 --- /dev/null +++ b/programs/manifest/verify-manifest.py @@ -0,0 +1,234 @@ +from enum import Enum +import subprocess +import io +import sys +import datetime +import time +import os +import json +import argparse + + +class VerificationResult(Enum): + Verified = 0 + Violated = 1 + Timeout = 2 + UnexpectedError = 3 + + def __str__(self): + return f"{self.name}" + + @staticmethod + def from_string(result: str) -> 'VerificationResult': + return VerificationResult[result] + + @staticmethod + def from_command_result(command_result: subprocess.CompletedProcess[str]) -> 'VerificationResult': + if command_result.returncode == 0: + assert "|Not violated" in command_result.stdout, "The verification terminated successfully, but cannot find the '|Not violated' substring in stdout" + return VerificationResult.Verified + elif "|Violated" in command_result.stdout: + # If the return code is not zero, and in the stdout we find `|Violated`, + # then the verification of the rule failed. + return VerificationResult.Violated + elif "|Timeout" in command_result.stdout: + # If the return code is not zero, and in the stdout we find `|Timeout`, + # then the verification of the rule failed. + return VerificationResult.Timeout + else: + # If the return code is not zero, but we cannot find `|Violated` in + # stdout, then we had an unexpected error while calling `just`. + return VerificationResult.UnexpectedError + + +class ProverOption: + '''Option to give to the prover.''' + pass + + +class Bmc(ProverOption): + def __init__(self, n: int): + self.n = n + + def __str__(self) -> str: + return f'--loop_iter {self.n}' + + +class AssumeUnwindCond(ProverOption): + def __str__(self) -> str: + return f'--optimistic_loop' + + +class CargoFeature: + '''Cargo feature to use when calling `cargo build-sbf`.''' + pass + + +class CvtDbMock(CargoFeature): + def __str__(self) -> str: + return 'cvt-db-mock' + + +class Rule: + def __init__(self, name: str, expected_result: VerificationResult, prover_options: list[ProverOption], cargo_features: list[CargoFeature]): + self.name = name + self.expected_result = expected_result + self.prover_options = prover_options + self.cargo_features = cargo_features + + def __str__(self) -> str: + return f"{self.name}" + + @staticmethod + def list_from_json(rules_config_file_path: str) -> list['Rule']: + with open(rules_config_file_path, 'r') as f: + data = json.load(f) + + rules = [] + for rule_data in data['rules']: + name = rule_data['name'] + + # Load expected result + expected_result_str = rule_data.get('expected_result') + if expected_result_str not in VerificationResult.__members__: + raise ValueError(f"Invalid expected result '{expected_result_str}' for rule '{name}'") + expected_result = VerificationResult.from_string( + expected_result_str) + + # Load prover options + prover_options = [] + for option in rule_data['prover_options']: + if 'bmc' in option: + if not isinstance(option['bmc'], int): + raise TypeError(f"Invalid type for 'bmc' option in rule '{name}', expected integer") + prover_options.append(Bmc(option['bmc'])) + elif 'assumeUnwindCond' in option: + if not isinstance(option['assumeUnwindCond'], bool): + raise TypeError(f"Invalid type for 'assumeUnwindCond' option in rule '{name}', expected boolean") + prover_options.append(AssumeUnwindCond()) + else: + raise ValueError(f"Unknown prover option '{option}' in rule '{name}'") + + # Load cargo features + cargo_features = [] + valid_features = { + 'cvt-db-mock': lambda: CvtDbMock() + } + for feature in rule_data['cargo_features']: + if feature not in valid_features: + raise ValueError(f"Unknown cargo feature '{feature}' in rule '{name}'") + else: + cargo_features.append(valid_features[feature]()) + + rules.append(Rule(name, expected_result, + prover_options, cargo_features)) + + return rules + + +class VerificationRunner: + def __init__(self): + self.had_error = False + + def verify_all(self, rules: list[Rule]): + logfile_name = VerificationRunner.generate_logfile_name() + with open(logfile_name, 'w') as logfile: + for (index, rule) in enumerate(rules): + print(f'[{index+1:2}/{len(rules)}] {rule.name} ... ', end='') + self.verify_rule(logfile, rule) + VerificationRunner.clean() + if self.had_error: + sys.exit(1) + + def verify_rule(self, logfile: io.TextIOWrapper, rule: Rule): + sys.stdout.flush() + command = VerificationRunner.build_command(rule) + try: + start_time = time.time() + verification_result = self.run_verification(command, logfile, rule) + end_time = time.time() # Record the end time + elapsed_time = end_time - start_time # Calculate the elapsed time + self.check_verification_result( + verification_result, rule.expected_result, command, elapsed_time) + except FileNotFoundError: + print(f"Failed to run command: `{command}`") + + @staticmethod + def build_command(rule: Rule) -> str: + command = f'just verify-remote {rule.name}' + for option in rule.prover_options: + command += f' {option}' + return command + + def run_verification(self, command: str, logfile: io.TextIOWrapper, rule: Rule) -> VerificationResult: + verification_env = VerificationRunner.build_verification_env(rule) + # Run the verifier and capure the output. + command_result = subprocess.run( + command.split(), check=False, text=True, capture_output=True, env=verification_env) + VerificationRunner.log_output( + logfile, rule.name, command, command_result) + return VerificationResult.from_command_result(command_result) + + @staticmethod + def build_verification_env(rule: Rule) -> dict[str, str]: + # Start with the current environment variables + verification_env = os.environ.copy() + cargo_features = '' + for feature in rule.cargo_features: + cargo_features += f' {feature}' + verification_env["CARGO_FEATURES"] = cargo_features + return verification_env + + def check_verification_result(self, verification_result: VerificationResult, expected_result: VerificationResult, command: str, elapsed_seconds: float) -> None: + # Assert that we did not have an unexpected error (i.e., compilation + # error), since all the rules should be verified or not verified. + assert verification_result != VerificationResult.UnexpectedError, \ + f"Had unexpected error running `{command}`" + + # A timeout is an unexpected event + assert verification_result != VerificationResult.Timeout, \ + f"Had a timeout event running `{command}`" + + if verification_result == expected_result: + print(f'ok ({elapsed_seconds:.2f}s)') + else: + print(f'error ({elapsed_seconds:.2f}s)') + print(f'\tExpected result: {expected_result}') + print(f'\tActual result: {verification_result}') + self.had_error = True + + @staticmethod + def generate_logfile_name() -> str: + current_time = datetime.datetime.now() + return current_time.strftime("log_verification_%Y-%m-%d_%H:%M:%S") + + @staticmethod + def log_output(logfile: io.TextIOWrapper, rule_name: str, command: str, command_result: subprocess.CompletedProcess[str]) -> None: + print(f'---- Rule {rule_name} ----', file=logfile) + print(f'Command: `{command}`', file=logfile) + print(f'Return code: {command_result.returncode}', file=logfile) + print(f'Stdout:', file=logfile) + print(command_result.stdout, file=logfile) + print(f'Stderr:', file=logfile) + print(command_result.stderr, file=logfile) + + @staticmethod + def clean() -> None: + ''' Call `just clean`. ''' + subprocess.run(["just", "clean"], check=True, capture_output=True) + + +# Parse the CLI options +parser = argparse.ArgumentParser( + description="Run the rules as specified in a JSON configuration file") +parser.add_argument( + "-r", "--rules", + help="Path to the JSON configuration file with the rules specification.", + type=str, + default="rules.json" +) +args = parser.parse_args() + +rules = Rule.list_from_json(args.rules) +runner = VerificationRunner() +runner.verify_all(rules) diff --git a/programs/ui-wrapper/tests/cases/place_order.rs b/programs/ui-wrapper/tests/cases/place_order.rs index c690f8b8c..49f001b73 100644 --- a/programs/ui-wrapper/tests/cases/place_order.rs +++ b/programs/ui-wrapper/tests/cases/place_order.rs @@ -573,10 +573,10 @@ async fn wrapper_place_order_without_globals_test() -> anyhow::Result<()> { let (quote_vault, _) = get_vault_address(&test_fixture.market.key, "e_mint); let (base_vault, _) = get_vault_address(&test_fixture.market.key, &base_mint); - let (global_base, _) = get_global_address(&base_mint); - let (global_quote, _) = get_global_address("e_mint); - let (global_base_vault, _) = get_global_vault_address(&base_mint); - let (global_quote_vault, _) = get_global_vault_address("e_mint); + let (_global_base, _) = get_global_address(&base_mint); + let (_global_quote, _) = get_global_address("e_mint); + let (_global_base_vault, _) = get_global_vault_address(&base_mint); + let (_global_quote_vault, _) = get_global_vault_address("e_mint); // place order let place_order_ix = Instruction { @@ -794,22 +794,22 @@ async fn wrapper_place_order_with_mixed_up_mint_ask() -> anyhow::Result<()> { let payer: Pubkey = test_fixture.payer(); let payer_keypair: Keypair = test_fixture.payer_keypair().insecure_clone(); - let (base_mint, trader_token_account_base) = test_fixture + let (base_mint, _trader_token_account_base) = test_fixture .fund_trader_wallet(&payer_keypair, Token::SOL, 1) .await; let (quote_mint, trader_token_account_quote) = test_fixture .fund_trader_wallet(&payer_keypair, Token::USDC, 1) .await; - let platform_token_account = test_fixture.fund_token_account("e_mint, &payer).await; - let referred_token_account = test_fixture.fund_token_account("e_mint, &payer).await; + let _platform_token_account = test_fixture.fund_token_account("e_mint, &payer).await; + let _referred_token_account = test_fixture.fund_token_account("e_mint, &payer).await; let (quote_vault, _) = get_vault_address(&test_fixture.market.key, "e_mint); - let (base_vault, _) = get_vault_address(&test_fixture.market.key, &base_mint); - let (global_base, _) = get_global_address(&base_mint); - let (global_quote, _) = get_global_address("e_mint); - let (global_base_vault, _) = get_global_vault_address(&base_mint); - let (global_quote_vault, _) = get_global_vault_address("e_mint); + let (_base_vault, _) = get_vault_address(&test_fixture.market.key, &base_mint); + let (_global_base, _) = get_global_address(&base_mint); + let (_global_quote, _) = get_global_address("e_mint); + let (_global_base_vault, _) = get_global_vault_address(&base_mint); + let (_global_quote_vault, _) = get_global_vault_address("e_mint); // place order as ask, but passing quote currency should fail let place_order_ix = Instruction { @@ -863,19 +863,19 @@ async fn wrapper_place_order_with_mixed_up_mint_bid() -> anyhow::Result<()> { let (base_mint, trader_token_account_base) = test_fixture .fund_trader_wallet(&payer_keypair, Token::SOL, 1) .await; - let (quote_mint, trader_token_account_quote) = test_fixture + let (quote_mint, _trader_token_account_quote) = test_fixture .fund_trader_wallet(&payer_keypair, Token::USDC, 1) .await; - let platform_token_account = test_fixture.fund_token_account("e_mint, &payer).await; - let referred_token_account = test_fixture.fund_token_account("e_mint, &payer).await; + let _platform_token_account = test_fixture.fund_token_account("e_mint, &payer).await; + let _referred_token_account = test_fixture.fund_token_account("e_mint, &payer).await; - let (quote_vault, _) = get_vault_address(&test_fixture.market.key, "e_mint); + let (_quote_vault, _) = get_vault_address(&test_fixture.market.key, "e_mint); let (base_vault, _) = get_vault_address(&test_fixture.market.key, &base_mint); - let (global_base, _) = get_global_address(&base_mint); - let (global_quote, _) = get_global_address("e_mint); - let (global_base_vault, _) = get_global_vault_address(&base_mint); - let (global_quote_vault, _) = get_global_vault_address("e_mint); + let (_global_base, _) = get_global_address(&base_mint); + let (_global_quote, _) = get_global_address("e_mint); + let (_global_base_vault, _) = get_global_vault_address(&base_mint); + let (_global_quote_vault, _) = get_global_vault_address("e_mint); // place order as ask, but passing quote currency should fail let place_order_ix = Instruction { diff --git a/programs/wrapper/src/processors/shared.rs b/programs/wrapper/src/processors/shared.rs index ca307bd0b..d7f5e47d3 100644 --- a/programs/wrapper/src/processors/shared.rs +++ b/programs/wrapper/src/processors/shared.rs @@ -16,7 +16,7 @@ use hypertree::{ use manifest::{ program::{get_dynamic_account, invoke}, quantities::BaseAtoms, - state::{claimed_seat::ClaimedSeat, MarketFixed, RestingOrder}, + state::{claimed_seat::ClaimedSeat, get_helper_seat, MarketFixed, RestingOrder}, validation::{ManifestAccountInfo, Program, Signer}, }; use solana_program::{ @@ -231,7 +231,7 @@ pub(crate) fn sync_fast( get_mut_helper::>(wrapper_dynamic_data, market_info_index) .get_mut_value(); let claimed_seat: &ClaimedSeat = - get_helper::>(market_ref.dynamic, market_info.trader_index).get_value(); + get_helper_seat(market_ref.dynamic, market_info.trader_index).get_value(); market_info.base_balance = claimed_seat.base_withdrawable_balance; market_info.quote_balance = claimed_seat.quote_withdrawable_balance; market_info.quote_volume = claimed_seat.quote_volume;